Spring Boot @ControllerAdvice异常处理

微服务框架(十四)

Posted by Tillend on December 4, 2018

  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。

  本文为Spring Boot使用@ControllerAdvice进行自定义异常捕捉及处理

本系列文章中所使用的框架版本为Spring Boot 2.0.3-RELEASE,Spring 5.0.7-RELEASE,Dubbo 2.6.2。

注解

@ControllerAdvice

控制器增强,所有在Controller中被抛出的异常将会被使用@ControllerAdvice注解的类捕捉。默认情况下, @ControllerAdvice全局应用于所有控制器的方法。

若所有异常处理类返回的数据格式为json,则可以使用 @RestControllerAdvice 代替 @ControllerAdvice @RestControllerAdvice = @ControllerAdvice + @ResponseBody

@ExceptionHandler

异常处理器,在特定异常处理程序的类或方法中以处理异常的注解。

业务异常

常规的Controller层,会抛出业务异常、校检异常等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping(value = "/")
public class TestController {

    @Autowired
    private BussinessService bussinessService ;

    @RequestMapping(value = "/basic", method = RequestMethod.POST)
    public Resp<RespVO> getBasic(@RequestBody @Valid ReqVO reqVO) {

		RespVO respVO = bussinessService.getResult(reqVO);
		ifrespVO  == null
			throws BussinessExceptionMapper.build("调用失败");

        return Resp.createSuccess(respVO);
    }
}

业务异常定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Setter
@Getter
public class BussinessException extends RuntimeException {

    public static final String UNKNOWN_EXCEPTION = "unknown.exception";
    public static final String SERVICE_FAIL = "service.fail";
    public static final String NETWORK_EXCEPTION = "network.exception";
    public static final String TIMEOUT_EXCEPTION = "timeout.exception";

    private String code;

    public BussinessException() {
        super();
    }

    public BussinessException(String message, Throwable cause) {
        super(message, cause);
    }

    public BussinessException(String message) {
        super(message);
    }

    public BussinessException(Throwable cause) {
        super(cause);
    }

    public BussinessException(String code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }

    public BussinessException(String code, String message) {
        super(message);
        this.code = code;
    }
}

业务异常构造类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BussinessExceptionMapper {

    public static BussinessException build(String message) {
        return build(BussinessException.SERVICE_FAIL, message);
    }

    public static BussinessException build(String code, String message) {
        return new BussinessException(code, message);
    }

    public static BussinessException build(String message, Throwable cause) {
        return build(BussinessException.SERVICE_FAIL, message, cause);
    }

    public static BussinessException build(String code, String message, Throwable cause) {
        return new BussinessException(code, message, cause);
    }

}

异常处理

@Controller@RestController中的异常捕捉及处理

参数校检异常

使用hibernate validator校检参数,@Valid注解抛出的校检异常为MethodArgumentNotValidException,在@ExceptionHandler注释的方法中捕捉第一个错误信息

1
2
3
4
5
6
7
8
9
10
11
12
13
@ResponseBody
@ExceptionHandler(value = MethodArgumentNotValidException.class)
public Resp validHandler(MethodArgumentNotValidException ex) {

    logger.error("ValidException:", ex);

    List<ObjectError> list = ex
            .getBindingResult().getAllErrors();

    String errorMsg = StringUtils.defaultString(list.get(0).getDefaultMessage(), "参数错误");

    return Resp.createError(RespCode.PARAM_ERR, "input.param.error", errorMsg);
}

业务异常

1
2
3
4
5
6
7
8
@ResponseBody
@ExceptionHandler(value = BussinessException.class)
public Resp bussinessHandler(BussinessException ex) {

    logger.error("BussinessException:", ex);

    return Resp.createError(RespCode.BUSINESS_INVALID, ex.getCode(), ex.getMessage());
}

Dubbo Rpc异常及运行时异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
@ControllerAdvice
public class GlobalControllerAdvice {

    private Logger logger = LoggerFactory.getLogger(GlobalControllerAdvice.class);

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public Resp validHandler(MethodArgumentNotValidException ex) {

        logger.error("ValidException:", ex);

        List<ObjectError> list = ex
                .getBindingResult().getAllErrors();

        String errorMsg = StringUtils.defaultString(list.get(0).getDefaultMessage(), "参数错误");

        return Resp.createError(RespCode.PARAM_ERR, "input.param.error", errorMsg);
    }

    @ResponseBody
    @ExceptionHandler(value = BussinessException.class)
    public Resp bussinessHandler(BussinessException ex) {

        logger.error("BussinessException:", ex);

        return Resp.createError(RespCode.BUSINESS_INVALID, ex.getCode(), ex.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(value = Exception.class)
    public Resp errorHandler(Exception e) {

        logger.error("uncaught Exception:", e);

        Resp resp;
        if (e instanceof RpcException) {
            resp = Resp.createError(RespCode.ERROR, "rpc.exception",
                    e.getMessage());
        } else if (e instanceof RuntimeException) {
            resp = Resp.createError(RespCode.ERROR, "runtime.exception",
                    "运行时异常,详见堆栈日志");
        } else {
            resp = Resp.createError(RespCode.BUSINESS_INVALID,
                    "service.fail", "服务失败");
        }

        return resp;
    }
}

参考资料:
1.ControllerAdvice
2.RestControllerAdvice