Spring Boot 中处理异常的正确方法
Correct way to handle exceptions in Spring Boot
我正在阅读 Spring 文档,发现从 ResponseEntityExceptionHandler
创建一个子 class 是处理异常的好方法。但是,我尝试以不同的方式处理异常,因为我需要将 BusinessExceptions
与 TechnicalExceptions
区分开来。
创建了一个名为 BusinessFault
的 bean,它封装了异常详细信息:
BusinessFault.java
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(value = Include.NON_NULL)
public class BusinessFault {
@JsonProperty(value = "category")
private final String CATEGORY = "Business Failure";
protected String type;
protected String code;
protected String reason;
protected String description;
protected String instruction;
public BusinessFault(String type, String code, String reason) {
this.type = type;
this.code = code;
this.reason = reason;
}
public BusinessFault(String type, String code, String reason, String description, String instruction) {
this.type = type;
this.code = code;
this.reason = reason;
this.description = description;
this.instruction = instruction;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getInstruction() {
return instruction;
}
public void setInstruction(String instruction) {
this.instruction = instruction;
}
public String getCATEGORY() {
return CATEGORY;
}
}
创建了一个 BusinessException
class,它通过其构造函数传递的详细信息创建一个 BusinessFault
beans 来完成工作:
BusinessException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
public abstract class BusinessException extends RuntimeException {
private BusinessFault businessFault;
public BusinessException(String type, String code, String reason) {
this.businessFault = new BusinessFault(type, code, reason);
}
public BusinessException(String type, String code, String reason, String description, String instruction) {
this.businessFault = new BusinessFault(type, code, reason, description, instruction);
}
public BusinessException(BusinessFault businessFault) {
this.businessFault = businessFault;
}
public BusinessFault getBusinessFault() {
return businessFault;
}
public void setBusinessFault(BusinessFault businessFault) {
this.businessFault = businessFault;
}
}
创建了一个特定的 UserNotFoundException
class,它从 BusinessException
class:
延伸
UserNotFoundException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
import com.rest.restwebservices.exception.map.ExceptionMap;
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(BusinessFault businessFault) {
super(businessFault);
}
public UserNotFoundException(String reason) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason);
}
public UserNotFoundException(String reason, String description, String instruction) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason, description,
instruction);
}
}
创建了一个 BusinessExceptionHandler
,但不是 ResponseEntityExceptionHandler
的子 class,它只有一个 @ControllerAdvice
注释和一个处理所有抛出的方法 BusinessExceptions
:
BusinessExceptionHandler.java
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.BusinessException;
import com.rest.restwebservices.exception.fault.BusinessFault;
@ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, BusinessException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.OK);
}
}
服务层可以抛出一个UserNotFoundException
:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
User user = userRepository.findOne(id);
if (user == null)
throw new UserNotFoundException("The ID " + id + " doesn't behave to any user!");
return user;
}
}
它工作正常。但我想知道这是否是处理异常的坏习惯?
我用类似的方式来处理异常。但在我的例子中,不同的处理程序根据错误状态进行管理(例如,用户存在,由于某些不满足条件而无法注册用户等)。
您还可以为某些特殊情况添加通用 BusinessException。希望它能帮助你感觉更好。
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.ResourceNotFoundException;
import com.rest.restwebservices.exception.PreconditionFailedException;
import com.rest.restwebservices.exception.ResourceAlreadyExistsException;
import com.rest.restwebservices.exception.fault.BusinessFault;
@ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceNotFoundException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(PreconditionFailedException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, PreconditionFailedExceptionex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.PRECONDITION_FAILED);
}
@ExceptionHandler(ResourceAlreadyExistsException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceAlreadyExistsException) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.CONFLICT);
}
}
你的异常处理有点问题。原则上,捕获 runtime exceptions
、处理它们并将它们发送给客户端是绝对可以的,客户端可能是使用您的 REST 服务并将错误响应作为 JSON 对象获取的人。如果您设法告诉他他做错了什么以及他可以做些什么,那就太好了!当然,它会增加一些复杂性,但使用 API.
可能很容易也很舒服
但也要考虑使用您的代码的后端开发人员。尤其是您的 UserService 中的 public User findById(Long id)
方法是晦涩难懂的。这样做的原因是您 BusinessException
,特别是 UserNotFoundException
未选中 。
如果我加入了你们的(后端)团队,并且我要使用该服务编写一些业务逻辑,我会非常确定我对该方法的期望:我传递一个 user ID 并返回 User
对象(如果找到)或 null
(如果未找到)。这就是为什么我会写这样的代码
User user = userService.findById("42A");
if (user == null) {
// create a User or return an error or null or whatever
} else {
// proceed
}
但是,我永远不会知道第一个条件永远不会成立,因为您永远不会 return null
。我怎么知道我必须捕获异常?
编译器是否告诉我要捕获它?没有,因为它没有被检查。
我会看看你的源代码吗? 见鬼,不!你的情况非常简单。一百行代码中的另一个class可能会在另一个方法中引发UserNotFoundException
。无论如何,有时我无法查看它的内部,因为 UserService
只是依赖项中编译的 class。
我看JavaDoc了吗?哈哈哈。比方说,50% 的时间我不会,而另外 50% 的时间你忘记记录它了,无论如何。
因此,开发人员必须等到他的代码被使用(无论是被客户端还是在单元测试中)时,才会发现它没有按预期工作,这迫使他重新设计迄今为止编写的代码。如果你的整个 API 都是这样设计的,那么未经检查的异常会突然冒出来,这可能会非常烦人,它会花费时间和金钱,而且很容易避免,实际上。
我正在阅读 Spring 文档,发现从 ResponseEntityExceptionHandler
创建一个子 class 是处理异常的好方法。但是,我尝试以不同的方式处理异常,因为我需要将 BusinessExceptions
与 TechnicalExceptions
区分开来。
创建了一个名为 BusinessFault
的 bean,它封装了异常详细信息:
BusinessFault.java
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import com.fasterxml.jackson.annotation.JsonProperty;
@JsonInclude(value = Include.NON_NULL)
public class BusinessFault {
@JsonProperty(value = "category")
private final String CATEGORY = "Business Failure";
protected String type;
protected String code;
protected String reason;
protected String description;
protected String instruction;
public BusinessFault(String type, String code, String reason) {
this.type = type;
this.code = code;
this.reason = reason;
}
public BusinessFault(String type, String code, String reason, String description, String instruction) {
this.type = type;
this.code = code;
this.reason = reason;
this.description = description;
this.instruction = instruction;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getReason() {
return reason;
}
public void setReason(String reason) {
this.reason = reason;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getInstruction() {
return instruction;
}
public void setInstruction(String instruction) {
this.instruction = instruction;
}
public String getCATEGORY() {
return CATEGORY;
}
}
创建了一个 BusinessException
class,它通过其构造函数传递的详细信息创建一个 BusinessFault
beans 来完成工作:
BusinessException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
public abstract class BusinessException extends RuntimeException {
private BusinessFault businessFault;
public BusinessException(String type, String code, String reason) {
this.businessFault = new BusinessFault(type, code, reason);
}
public BusinessException(String type, String code, String reason, String description, String instruction) {
this.businessFault = new BusinessFault(type, code, reason, description, instruction);
}
public BusinessException(BusinessFault businessFault) {
this.businessFault = businessFault;
}
public BusinessFault getBusinessFault() {
return businessFault;
}
public void setBusinessFault(BusinessFault businessFault) {
this.businessFault = businessFault;
}
}
创建了一个特定的 UserNotFoundException
class,它从 BusinessException
class:
UserNotFoundException.java
import com.rest.restwebservices.exception.fault.BusinessFault;
import com.rest.restwebservices.exception.map.ExceptionMap;
public class UserNotFoundException extends BusinessException {
public UserNotFoundException(BusinessFault businessFault) {
super(businessFault);
}
public UserNotFoundException(String reason) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason);
}
public UserNotFoundException(String reason, String description, String instruction) {
super(ExceptionMap.USERNOTFOUND.getType(), ExceptionMap.USERNOTFOUND.getCode(), reason, description,
instruction);
}
}
创建了一个 BusinessExceptionHandler
,但不是 ResponseEntityExceptionHandler
的子 class,它只有一个 @ControllerAdvice
注释和一个处理所有抛出的方法 BusinessExceptions
:
BusinessExceptionHandler.java
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.BusinessException;
import com.rest.restwebservices.exception.fault.BusinessFault;
@ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
@ExceptionHandler(BusinessException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, BusinessException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.OK);
}
}
服务层可以抛出一个UserNotFoundException
:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public User findById(Long id) {
User user = userRepository.findOne(id);
if (user == null)
throw new UserNotFoundException("The ID " + id + " doesn't behave to any user!");
return user;
}
}
它工作正常。但我想知道这是否是处理异常的坏习惯?
我用类似的方式来处理异常。但在我的例子中,不同的处理程序根据错误状态进行管理(例如,用户存在,由于某些不满足条件而无法注册用户等)。
您还可以为某些特殊情况添加通用 BusinessException。希望它能帮助你感觉更好。
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import com.rest.restwebservices.controller.UserController;
import com.rest.restwebservices.exception.ResourceNotFoundException;
import com.rest.restwebservices.exception.PreconditionFailedException;
import com.rest.restwebservices.exception.ResourceAlreadyExistsException;
import com.rest.restwebservices.exception.fault.BusinessFault;
@ControllerAdvice(basePackageClasses = UserController.class)
public class BusinessExceptionHandler {
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceNotFoundException ex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.NOT_FOUND);
}
@ExceptionHandler(PreconditionFailedException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, PreconditionFailedExceptionex) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.PRECONDITION_FAILED);
}
@ExceptionHandler(ResourceAlreadyExistsException.class)
@ResponseBody
public ResponseEntity<BusinessFault> genericHandler(HttpServletRequest request, ResourceAlreadyExistsException) {
return new ResponseEntity<BusinessFault>(ex.getBusinessFault(), HttpStatus.CONFLICT);
}
}
你的异常处理有点问题。原则上,捕获 runtime exceptions
、处理它们并将它们发送给客户端是绝对可以的,客户端可能是使用您的 REST 服务并将错误响应作为 JSON 对象获取的人。如果您设法告诉他他做错了什么以及他可以做些什么,那就太好了!当然,它会增加一些复杂性,但使用 API.
但也要考虑使用您的代码的后端开发人员。尤其是您的 UserService 中的 public User findById(Long id)
方法是晦涩难懂的。这样做的原因是您 BusinessException
,特别是 UserNotFoundException
未选中 。
如果我加入了你们的(后端)团队,并且我要使用该服务编写一些业务逻辑,我会非常确定我对该方法的期望:我传递一个 user ID 并返回 User
对象(如果找到)或 null
(如果未找到)。这就是为什么我会写这样的代码
User user = userService.findById("42A");
if (user == null) {
// create a User or return an error or null or whatever
} else {
// proceed
}
但是,我永远不会知道第一个条件永远不会成立,因为您永远不会 return null
。我怎么知道我必须捕获异常?
编译器是否告诉我要捕获它?没有,因为它没有被检查。
我会看看你的源代码吗? 见鬼,不!你的情况非常简单。一百行代码中的另一个class可能会在另一个方法中引发UserNotFoundException
。无论如何,有时我无法查看它的内部,因为 UserService
只是依赖项中编译的 class。
我看JavaDoc了吗?哈哈哈。比方说,50% 的时间我不会,而另外 50% 的时间你忘记记录它了,无论如何。
因此,开发人员必须等到他的代码被使用(无论是被客户端还是在单元测试中)时,才会发现它没有按预期工作,这迫使他重新设计迄今为止编写的代码。如果你的整个 API 都是这样设计的,那么未经检查的异常会突然冒出来,这可能会非常烦人,它会花费时间和金钱,而且很容易避免,实际上。