基于spring的异常一站式解决方案

https://segmentfault.com/a/1190000006749441#articleHeader4

https://lrwinx.github.io/2016/04/28/%E5%A6%82%E4%BD%95%E4%BC%98%E9%9B%85%E7%9A%84%E8%AE%BE%E8%AE%A1java%E5%BC%82%E5%B8%B8/

1,异常分类

  1,继承RuntimeException子类,比如nullPointException,称非受检异常,不要求写try/catch语句

   2,其他异常都是,比如数据库连接异常,称受检异常,要求显示写try/catch语句

    我们如何选择我们的异常种类,就一句,如果你的这个服务的编写者,你希望服务者显式调用try/catch语句,就抛出受检异常。

但异常一定要接受的,不管是受检异常还是非受检异常。当你的非受检异常没有接受,就会一直往上面抛出,最后都没有人接受,

如果应用是单线程的,整个应用就会停掉,在tomcat中不会停是因为tomcat有让这个应用恢复过来的功能。

2,入参约束

maven导入,@vaild是jsr303的标准,hibernate-validator是它的实现,在方法上的注解@validated是spring-context的注解

<dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate.validator</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.13.Final</version>
        </dependency>
View Code

 之前一直在考虑,入参约束在controller做还是service做,最后发现service一定要做,因为service会互相调用,

在其他service调用是难免会有不正确的入参,但是我们仍然可以在controller做,多一重保险

controller

 @PostMapping("/insertAccessories")
    public SuccessResponse<Object> insertAccessories(@RequestBody @Valid DataRequest<AccessoriesDO> dataRequest) {
        accessoriesService.insertAccessories(dataRequest.getBody());
        return new SuccessResponse<Object>("0", "成功", null);
    }

service (上面有@validated

@Validated
public interface AccessoriesService {

    /**
     * @author      : kooing
     * @Date        : 2018/4/22 13:24
     * @Desription  : 增加辅料
     * @return      :
     */
    public void insertAccessories(@Valid AccessoriesDO accessoriesDO);

在实体里面或DTO加上你的约束

 @NotEmpty(message="姓名不能为空")
    private String memberUsername;
    @NotEmpty(message="密码不能为空")
    private String memberPassword;

3,异常抛出和捕获

所有非检查异常的基类

@Data
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseServiceException extends RuntimeException {
    private String code;
    private String message;
}

后面继承他的要重写有参构造方法,可以一个模块一个异常类,也可以细分一点一个异常一个异常类

@Data
public class AccessoriesException extends BaseServiceException {
    public AccessoriesException(String errorCode, String errorMsg) {
        super(errorCode, errorMsg);
    }
}

如何捕获异常,在controller用@ExceptionHandler注解能捕获,但代码会冗余在一起,我是放在多个全局异常捕获,但是有个全局异常捕获会

捕获基类异常的,如果spring配到是这个异常(或者他的父类,过程像catch一样)就不会继续需要更加切合的异常了,所有这个全局异常捕获

有个优先级问题,最后我的方案是,最后业务的异常捕获不使用全局捕获,写在另外controller里面,再由业务的controller继承他,(勉强实现

了代码分离和优先级的问题)

@RestController
@Slf4j
public class AccessoriesExceptionHandler {

    @ExceptionHandler(AccessoriesException.class)
    public Object BaseServiceException(HttpServletRequest req, BaseServiceException e) {
        log.error("---AccessoriesException Handler---Host {} invokes url {} CODE:{}  MESSAGE: {}", req.getRemoteHost()
                , req.getRequestURL()
                , e.getCode()
                , e.getMessage());
        ExceptionResponse exceptionResponse = new ExceptionResponse();
        exceptionResponse.setCode(e.getCode());
        exceptionResponse.setMessage(e.getMessage());
        return exceptionResponse;
    }
}
@Slf4j
@RestController
@RequestMapping("accessoriesRecord")
public class AccessoriesRecordController extends AccessoriesExceptionHandler{

全局异常捕获类,下面我分别捕获了404异常,controller入参异常,service入参异常,业务异常(没有对应的exceptionHandler),和Exception(避免应用关闭,但调试的时候注释掉,方便看报错)

@RestController
@ControllerAdvice
@Slf4j
public class GlobalExceptionHandler {

    @ExceptionHandler(value = NoHandlerFoundException.class)
    public Object noHandlerFoundException(HttpServletRequest req, Exception e) throws Exception {
        log.error("---404 Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
        return new ExceptionResponse(GlobalCode.CODE_404, GlobalCode.MSG_404);
    }

    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Object bindException(HttpServletRequest req, Exception e) throws Exception {
        log.error("---controller---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
        return new ExceptionResponse(GlobalCode.CODE_CONTROLLER, GlobalCode.MSG_CONTROLLER);
    }

    @ExceptionHandler(value = ConstraintViolationException.class)
    public Object methodArgumentNotValidException(HttpServletRequest req, Exception e) throws Exception {
        log.error("---service---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
        return new ExceptionResponse(GlobalCode.CODE_SERVICE, GlobalCode.MSG_SERVICE);
    }

    @ExceptionHandler(BaseServiceException.class)
    public Object BaseServiceException(HttpServletRequest req, BaseServiceException e) {
        log.error("---service Exception Handler---Host {} invokes url {} CODE:{}  MESSAGE: {}", req.getRemoteHost()
                , req.getRequestURL()
                , e.getCode()
                , e.getMessage());
        ExceptionResponse exceptionResponse = new ExceptionResponse();
        exceptionResponse.setCode(e.getCode());
        exceptionResponse.setMessage(e.getMessage());
        return exceptionResponse;
    }


//    @ExceptionHandler(value = Exception.class)
//    public Object defaultErrorHandler(HttpServletRequest req, Exception e)  {
//        log.error("---DefaultException Handler---Host {} invokes url {} ERROR: {}", req.getRemoteHost(), req.getRequestURL(), e.getMessage());
//        return new ExceptionResponse(GlobalCode.CODE_UNKNOWN, GlobalCode.MSG_UNKNOWN);
//    }

}

异常错误码,设计了两个string类的code和mssage,用总的异常码,和模块异常码

public class ErrorCodeBase {
    public static final long Global = 10000L;
    public static final long ACCESSION = 20000L;
    public static final long MATERIAL = 30000L;
    public static final long MEMBER = 40000L;
    public static final long PACKGE_IT = 50000L;
    public static final long PRODUCT = 60000L;
}
public class GlobalCode {
    public static final String CODE_CONTROLLER = String.valueOf(ErrorCodeBase.Global + 1L);
    public static final String MSG_CONTROLLER = "控制层入参错误";

    public static final String CODE_SERVICE = String.valueOf(ErrorCodeBase.Global + 2L);
    public static final String MSG_SERVICE = "服务入参错误";

    public static final String CODE_404 = String.valueOf(ErrorCodeBase.Global + 3L);
    public static final String MSG_404 = "没有这个api接口";

    public static final String CODE_UNKNOWN = String.valueOf(ErrorCodeBase.Global + 4L);
    public static final String MSG_UNKNOWN = "服务器未知错误";
}
public class ResultCode {
    public static final String CODE_NUMBER = String.valueOf(ErrorCodeBase.ACCESSION + 1L);
    public static final String MSG_NUMBER = "数量不够";

    public static final String CODE_RECORD = String.valueOf(ErrorCodeBase.ACCESSION + 2L);
    public static final String MSG_RECORD = "没有这个辅料出入库纪录";
}

最后补上一个抛出异常的方法和一个服务的文件目录结构

if(accessoriesRecordDOTemp == null){
            throw new AccessoriesException(ResultCode.CODE_RECORD, ResultCode.CODE_RECORD);
        }

原文地址:https://www.cnblogs.com/vhyc/p/8834913.html