Spring Boot中处理异常的正确方法

Mat*_*llo 6 java spring spring-boot spring-restcontroller spring-rest

我正在阅读 Spring 文档,发现从创建子类ResponseEntityExceptionHandler是处理异常的好方法。但是,我尝试以不同的方式处理异常,因为我需要BusinessExceptionsTechnicalExceptions.

创建了一个BusinessFault封装异常详细信息的 bean :

业务故障.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

创建了一个BusinessException类,它BusinessFault通过通过其构造函数传递的详细信息创建一个bean 来完成这项工作:

业务异常.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;
    }
}
Run Code Online (Sandbox Code Playgroud)

创建了一个特定的UserNotFoundException类,它从BusinessException类扩展而来:

用户未找到异常.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

创建了一个BusinessExceptionHandler,但它不是 的子类ResponseEntityExceptionHandler,它只有一个@ControllerAdvice注释和一个处理所有抛出的方法BusinessExceptions

业务异常处理程序

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

服务层可以抛出一个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;
    }
}
Run Code Online (Sandbox Code Playgroud)

它工作正常。但我想知道这是否是处理异常的坏习惯?

Mat*_*att 5

我对你的异常处理有一个小问题。原则上,捕获runtime exceptions、处理它们并将它们发送到客户端是绝对可以的,客户端可能使用您的 REST 服务并以 JSON 对象的形式获取错误响应。如果你能告诉他他做错了什么以及他可以采取什么措施,那就太好了!当然,这会增加一些复杂性,但使用该 API 可能会很容易且舒适。

但也要考虑一下使用您的代码的后端开发人员。尤其是public User findById(Long id)你的 UserService 中的方法是晦涩难懂的。这样做的原因是你让你的BusinessException,特别是,UserNotFoundException 不受控制的

如果我加入你的(后端)团队,并且我要使用该服务编写一些业务逻辑,我会非常确定我必须从该方法中得到什么:我传递一个用户 IDUser并在找到一个对象时返回一个对象或者null如果没有。这就是为什么我会写这样的代码

User user = userService.findById("42A");
if (user == null) {
  // create a User or return an error or null or whatever
} else {
  // proceed
}
Run Code Online (Sandbox Code Playgroud)

然而,我永远不会知道,第一个条件永远不会成立,因为你再也不会回来了null。我怎么知道我必须捕获异常?

编译器是否告诉我要捕获它?不,因为没有检查。

我可以看看你的源代码吗?一定不行!你的情况非常简单。这UserNotFoundException可能会在另一个类的另一个方法中在数百行代码中提出。无论如何,有时我无法查看它的内部,因为那UserService只是依赖项中的已编译类。

我阅读了 JavaDoc 吗?哈哈哈。比方说,50% 的情况下我不会,而另外 50% 的情况下你无论如何都会忘记记录它。

因此,开发人员必须等到他的代码被使用(无论是由客户端还是在单元测试中)才能看到它没有按他的预期工作,从而迫使他重新设计迄今为止编写的代码。如果你的整个 API 都是这样设计的,那么未经检查的异常会突然出现,这可能会非常非常烦人,它会花费时间和金钱,而且实际上很容易避免。