接上篇文章的统一数据格式返回…
文章目录
- 1. 统一异常处理
- 1.1 使用
- 2. 统一数据返回和统一异处理是怎么实现的
- 2.1 `initHandleAdapters`
- 2.2 `initHandleExceptionResolvers`
1. 统一异常处理
1.1 使用
统一异常处理的两个关键的注解是@ControllerAdvice
+ @ExceptionHandler
@ControllerAdvice
表示控制器通知类@ExceptionHandler
:是异常处理器
两者结合就表示:出现异常的时候执行某个通知,也就是执行某个方法具体的代码如下:
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handleException(Exception e) {
//表示代码如果出现Exception(及其之类)异常,就返回Result的对象
return Result.fail(e.getMessage());
}
}
其中Result是自定义的放回结果类:
同时,我们可以针对不同的异常,返回不同的结果
@ControllerAdvice
@ResponseBody
public class ErrorAdvice {
@ExceptionHandler
public Object handleException(Exception e) {
//表示代码如果出现Exception(及其之类)异常,就返回Result的对象
return Result.fail(e.getMessage());
}
@ExceptionHandler
public Object handleException(ArithmeticException e) {
return Result.fail("算术异常" + e.getMessage());
}
@ExceptionHandler
public Object handleException(NullPointerException e) {
return Result.fail("空指针异常" + e.getMessage());
}
}
测试:
@RequestMapping("/test")
@RestController
public class TestController {
@RequestMapping("/Test1")
public String Test1() {
int a = 10 / 0;
return "t1";
}
@RequestMapping("Test2")
public String Test2() {
int[] arr = new int[4];
int b = arr[4];
return "t2";
}
}
2. 统一数据返回和统一异处理是怎么实现的
统一数据返回和统一异处理是怎么实现的?
我们从DispatcherServlet
的代码开始分析
当Tomcat启动的时候,有一个核心的类DispatcherServlet,用来控制程序的执行顺序
这个对象在创建的时候,会初始化一系列的对象:
其中与统一数据返回和统一异常处理相关的就是initHandleAdapters
和initHandleExceptionResolvers
2.1 initHandleAdapters
这个⽅法在执时会查找使用所有的 @ControllerAdvice
类,把 ResponseBodyAdvice
类
放在容器中,当发⽣某个事件时,调⽤相应的Advice⽅法,⽐如返回数据前调⽤统⼀数据封装
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
//查找所有被@ControllerAdvice注解的Bean
List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
List<Object> requestResponseBodyAdviceBeans = new ArrayList<>();
for (ControllerAdviceBean adviceBean : adviceBeans) {
Class<?> beanType = adviceBean.getBeanType();
if (beanType == null) {
throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
}
Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(adviceBean, attrMethods);
}
Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(adviceBean, binderMethods);
}
//遍历adviceBeans列表,检查每个ControllerAdviceBean对应的bean类型是否实现了RequestBodyAdvice或ResponseBodyAdvice接口
if (RequestBodyAdvice.class.isAssignableFrom(beanType) || ResponseBodyAdvice.class.isAssignableFrom(beanType)) {
requestResponseBodyAdviceBeans.add(adviceBean);
}
}
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
if (logger.isDebugEnabled()) {
int modelSize = this.modelAttributeAdviceCache.size();
int binderSize = this.initBinderAdviceCache.size();
int reqCount = getBodyAdviceCount(RequestBodyAdvice.class);
int resCount = getBodyAdviceCount(ResponseBodyAdvice.class);
if (modelSize == 0 && binderSize == 0 && reqCount == 0 && resCount == 0) {
logger.debug("ControllerAdvice beans: none");
}
else {
logger.debug("ControllerAdvice beans: " + modelSize + " @ModelAttribute, " + binderSize +
" @InitBinder, " + reqCount + " RequestBodyAdvice, " + resCount + " ResponseBodyAdvice");
}
}
}
2.2 initHandleExceptionResolvers
initHandleExceptionResolvers
方法会取得所有实现了HandleExceeptionResolver
接口的Bean,其中就有一个ExceptionHandlerExceptionResolver
的bean,这个Bean在应用启动的时候或获取到所有被注解@ControllerAdvice
标注的bean对象,做进一步处理
当Controller抛出异常的时候,DispatcherServlet
使用ExceptionHandlerExceptionResolver
来解析异常,而ExceptionHandlerExceptionResolver
通过ExceptionHandlerMethodResolver
来解析异常
@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
List<Class<? extends Throwable>> matches = new ArrayList<>();
for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
if (mappedException.isAssignableFrom(exceptionType)) {
matches.add(mappedException);
}
}
if (!matches.isEmpty()) {
if (matches.size() > 1) {
matches.sort(new ExceptionDepthComparator(exceptionType));
}
return this.mappedMethods.get(matches.get(0));
}
else {
return NO_MATCHING_EXCEPTION_HANDLER_METHOD;
}
}
我们通过调试来看处理异常的整个过程
断点打在这个地方(第一次执行到的时候不是我们想要的,按f9会再次跳到这个地方第二次执行)
往下执行,就会发现此时正在遍历mappedMethods,里面就是我们在ErrorAdvice里面指定的所有异常
遍历完后,实际上就是筛选出哪些异常能够处理我们当前的异常类型,由于我们当前是算术异常,那么能匹配的就留下了两个
调用sort方法之后,就会按照优先级排序好,以便我们更精确的处理异常