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

时间:2018-05-28 18:36:46

标签: java spring spring-boot spring-restcontroller spring-rest

我正在阅读Spring文档,发现从ResponseEntityExceptionHandler创建子类是处理异常的好方法。但是,我尝试以不同的方式处理异常,因为我需要从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类,它通过构造函数传递的详细信息创建BusinessFault bean来完成这项工作:

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类,该类从BusinessException类扩展:

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的子类,而是只有@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;
    }
}

工作正常。但我想知道这是否是处理例外的不良做法?

2 个答案:

答案 0 :(得分:2)

我的异常处理有点问题。原则上可以捕获runtime exceptions,处理它们并将它们发送给客户端,这可能是使用您的REST服务并将错误响应作为JSON对象的人。如果你设法告诉他他做错了什么以及他能做些什么,太棒了!当然,它会增加一些复杂性,但使用该API可能很容易和舒适。

但是考虑后端开发人员也可以使用你的代码。特别是UserService中的public User findById(Long id)方法模糊不清。这样做的原因是您使BusinessException,尤其是UserNotFoundException 取消选中

如果我加入了您的(后端)团队,并且我要使用该服务编写一些业务逻辑,我将非常确定我对该方法的期望:我传递用户ID 如果找到User对象,则返回;如果没有,则返回null。这就是我写代码的原因

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

然而,我永远不会知道,第一个条件永远不会成立,因为你永远不会返回null。我怎么知道我必须抓住一个例外?

编译器是否告诉我抓住它?不,因为没有检查。

我会查看您的源代码吗?见鬼,不!你的情况非常简单。可以在100行代码中的另一个类中的另一个方法中引发UserNotFoundException。无论如何,有时我无法查看它,因为UserService只是依赖项中的编译类。

我是否阅读过JavaDoc?哈哈哈。让我们说,无论如何,50%的时间我不会,而另外50%你忘记记录它。

因此,开发人员必须等到他的代码被使用(通过客户端或单元测试)才能看到它不能按预期工作,迫使他重新设计到目前为止他编写的代码。如果您的整个API都采用这种方式设计,那么未经检查的异常会突然冒出来,它可能非常烦人,实际上它需要花费时间和金钱而且很容易避免。

答案 1 :(得分:1)

我使用类似的方法来处理异常。但在我的情况下,根据错误状态管理不同的处理程序(例如,存在用户,由于某些不满意的条件而无法注册用户等)。

您还可以为某些特殊情况添加通用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);
    }
}