@ControllerAdvice是在类上声明的注解,其用法主要有三点:
- @ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
- @InitBinder注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;
- @ModelAttribute注解标注的方法:表示此方法会在执行目标Controller方法之前执行 。
@ExceptionHandler
使用@ExceptionHandler注解来拦截一个异常,并进行处理
创建一个报错的接口
@RestController
@RequestMapping("/v1/test")
@Slf4j
public class TestController {
@GetMapping("/err")
public ResponseEntity<String> errTest() {
int a = 1 / 0;
return new ResponseEntity<>("OK", HttpStatus.OK);
}
}
该接口报的错为 java.lang.ArithmeticException: / by zero
,一个算术异常

捕获处理该异常
/**
* @author Jenson
*/
@Slf4j
@ControllerAdvice
public class BaseControllerAdvice {
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<ExceptionResponse> processArithmeticException(HttpServletRequest request, HandlerMethod method, ArithmeticException exception) {
if (log.isWarnEnabled()) {
log.warn("ArithmeticException :", exception);
}
ExceptionResponse er = new ExceptionResponse();
er.setFailed(Boolean.TRUE);
er.setCode("err.arithmetic");
er.setType("error");
er.setMessage("算术异常: " + exception.getMessage());
return new ResponseEntity<>(er, HttpStatus.OK);
}
}
创建了一个ExceptionResponse来组装异常响应
/**
* @author Jenson
*/
@Data
public class ExceptionResponse {
public static final String FILED_FAILED = "failed";
public static final String FILED_CODE = "code";
public static final String FILED_MESSAGE = "message";
public static final String FILED_TYPE = "type";
private Boolean failed;
private String code;
private String message;
private String type;
}
结果,返回自定义异常

如果有两个ControllerAdvice
测试
有两个ControllerAdvice,是两个都生效还是只生效一个?先后顺序是怎样的?
增加一个自定义异常类
/**
* @author Jenson
*/
public class CommonException extends RuntimeException {
private final String code;
public CommonException(String code) {
super(code);
this.code = code;
}
public String getCode() {
return code;
}
}
调整BaseControllerAdvice
,增加一个CuxControllerAdvice
,在两个中都处理了CommonException.class
,如下
/**
* @author Jenson
*/
@Slf4j
@ControllerAdvice
public class BaseControllerAdvice {
@ExceptionHandler(ArithmeticException.class)
public ResponseEntity<ExceptionResponse> processArithmeticException(HttpServletRequest request, HandlerMethod method, ArithmeticException exception) {
if (log.isWarnEnabled()) {
log.warn("ArithmeticException :", exception);
}
ExceptionResponse er = new ExceptionResponse();
er.setFailed(Boolean.TRUE);
er.setCode("err.arithmetic");
er.setType("error");
er.setMessage("算术异常: " + exception.getMessage());
return new ResponseEntity<>(er, HttpStatus.OK);
}
@ExceptionHandler(CommonException.class)
public ResponseEntity<ExceptionResponse> processCommonException(HttpServletRequest request, HandlerMethod method, CommonException exception) {
log.warn("CommonException22222 :", exception);
ExceptionResponse er = new ExceptionResponse();
er.setFailed(Boolean.TRUE);
er.setCode(exception.getCode());
er.setType("error");
er.setMessage("通用异常222222: " + exception.getMessage());
return new ResponseEntity<>(er, HttpStatus.OK);
}
}
/**
* @author Jenson
*/
@Slf4j
@ControllerAdvice
public class CuxControllerAdvice {
@ExceptionHandler(CommonException.class)
public ResponseEntity<ExceptionResponse> processCommonException(HttpServletRequest request, HandlerMethod method, CommonException exception) {
log.warn("CommonException111111 :", exception);
ExceptionResponse er = new ExceptionResponse();
er.setFailed(Boolean.TRUE);
er.setCode(exception.getCode());
er.setType("error");
er.setMessage("通用异常11111: " + exception.getMessage());
return new ResponseEntity<>(er, HttpStatus.OK);
}
}
调整controller接口调用,报出自定义异常
/**
* @author Jenson
*/
@RestController
@RequestMapping("/v1/test")
@Slf4j
public class TestController {
@GetMapping("/err")
public ResponseEntity<String> errTest(@RequestParam String code) {
if("zero".equals(code)){
int a = 1 / 0;
}
else if("common".equals(code)){
throw new CommonException("err.initiative");
}
return new ResponseEntity<>("OK", HttpStatus.OK);
}
}
- 调用接口测试

日志
2022-02-21 16:02:43.926 WARN 21704 --- [nio-8010-exec-5] com.jenson.infra.BaseControllerAdvice : CommonException22222 :
com.jenson.exception.CommonException: err.initiative
at com.jenson.controller.v1.TestController.errTest(TestController.java:31) ~[classes/:na]
...
从日志中可以看出,只进入了BaseControllerAdvice
中的异常捕获
通过@Order来更改加载顺序
修改BaseControllerAdvice
和CuxControllerAdvice
如下,增加@Order
注解让BaseControllerAdvice
先加载
/**
* @author Jenson
*/
@Slf4j
@ControllerAdvice
@Order(2)
public class BaseControllerAdvice {
...
/**
* @author Jenson
*/
@Slf4j
@ControllerAdvice
@Order(1)
public class CuxControllerAdvice {
...
- 调用接口结果如下:
自定义异常:

日志
2022-02-21 16:10:03.109 WARN 22061 --- [nio-8010-exec-2] com.jenson.infra.CuxControllerAdvice : CommonException111111 :
com.jenson.exception.CommonException: err.initiative
at com.jenson.controller.v1.TestController.errTest(TestController.java:31) ~[classes/:na]
...
算术异常:
算术异常的捕获只写在了BaseControllerAdvice
中,依然被执行到了

日志
2022-02-21 16:11:28.065 WARN 22061 --- [nio-8010-exec-5] com.jenson.infra.BaseControllerAdvice : ArithmeticException :
java.lang.ArithmeticException: / by zero
at com.jenson.controller.v1.TestController.errTest(TestController.java:28) ~[classes/:na]
...
- 结论
当有多个@ControllerAdvice
时,如果某个异常的捕获在多个类中,只会进入先加载的类的异常捕获方法中,如果先加载的类中没有某个异常的捕获但后加载的有,则会进入后加载的异常捕获方法中。
@ModelAttribute
/**
* @author Jenson
*/
@ControllerAdvice
public class BaseModelAttribute {
@ModelAttribute("randomUUID")
public String generateRandomUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
}
/**
* @author Jenson
*/
@RestController
@RequestMapping("/v1/test")
@Slf4j
public class TestController {
@GetMapping("/hello")
public ResponseEntity<String> hello(@ModelAttribute("randomUUID") String randomStr) {
return ResponseEntity.ok("hello fool : "+randomStr);
}
...
测试结果:

注:调用参数中未使用@ModelAttribute
注解的Controller前也会进入标记了@ModelAttribute
的函数中。
@InitBinder
创建一个接口参数对象
/**
* @author Jenson
*/
@Data
public class ThingDTO {
private List<String> things;
}
创建一个接口来测试下
/**
* @author Jenson
*/
@RestController
@RequestMapping("/v1/test")
@Slf4j
public class TestController {
@GetMapping("/split2")
public ResponseEntity<ThingDTO> splitTest2(@RequestParam ThingDTO thing) {
return new ResponseEntity<>(thing, HttpStatus.OK);
}
...
参考org.springframework.beans.propertyeditors.CustomDateEditor
写一个自己的Editor
将接口传入的文本转为ThingDTO类型
/**
* @author Jenson
*/
public class CustomThingsEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
ThingDTO thingDTO = new ThingDTO();
List<String> list = new ArrayList<>(Arrays.asList(text.split("-")));
thingDTO.setThings(list);
this.setValue(thingDTO);
}
}
initBinder
/**
* @author Jenson
*/
@Slf4j
@ControllerAdvice
public class BaseInitBinder {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.registerCustomEditor(ThingDTO.class, new CustomThingsEditor());
}
}
测试接口

代码地址:https://gitee.com/jenson343/hotchpotch/tree/master/controller-advice-test
参考:
作者:YLiuY
链接:https://www.jianshu.com/p/acf7fdf20326
来源:简书
网友评论