在Spring里,我们可以使用@ControllerAdvice来声明一些全局性的东西,最常见的是结合@ExceptionHandler注解用于全局异常的处理。
@ControllerAdvice是在类上声明的注解,其用法主要有三点:
- @ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
- @InitBinder注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;
- @ModelAttribute注解标注的方法:表示此方法会在执行目标Controller方法之前执行 。
看下具体用法:
// 这里@RestControllerAdvice等同于@ControllerAdvice + @ResponseBody
在Controller里取出@ModelAttribute标注的方法返回的UserDetails对象:
RestController
这里当入参为examOpDate=2019-06-10时,Spring会使用我们上面@InitBinder注册的类型转换器将2019-06-10转换examOpDate对象:
@PostMapping
@ExceptionHandler标注的多个方法分别表示只处理特定的异常。这里需要注意的是当Controller抛出的某个异常多个@ExceptionHandler标注的方法都适用时,Spring会选择最具体的异常处理方法来处理,也就是说@ExceptionHandler(Exception.class)这里标注的方法优先级最低,只有当其它方法都不适用时,才会来到这里处理。
下面我们看看Spring是怎么实现的,首先前端控制器DispatcherServlet对象在创建时会初始化一系列的对象:
public
对于@ControllerAdvice 注解,我们重点关注initHandlerAdapters(context)和initHandlerExceptionResolvers(context)这两个方法。
initHandlerAdapters(context)方法会取得所有实现了HandlerAdapter接口的bean并保存起来,其中就有一个类型为RequestMappingHandlerAdapter的bean,这个bean就是@RequestMapping注解能起作用的关键,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:
public
经过这里处理之后,@ModelAttribute和@InitBinder就能起作用了,至于DispatcherServlet和RequestMappingHandlerAdapter是如何交互的这就是另一个复杂的话题了,此处不赘述。
再来看DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:
public
当Controller抛出异常时,DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注的方法是这里:
public
整个@ControllerAdvice处理的流程就是这样,这个设计还是非常灵活的。
源码github地址,各位同学可以试着去运行下项目熟悉这个流程:
jufeng98/java-mastergithub.com![v2-0849d83edf67eb757240e58424966d0f_ipico.jpg](http://img-03.proxy.5ce.com/view/image?&type=2&guid=37c4f764-e52f-eb11-8da9-e4434bdf6706&url=https://pic4.zhimg.com/v2-0849d83edf67eb757240e58424966d0f_ipico.jpg)