Spring的@ControllerAdvice注解作用原理探究(转发)
在Spring里,我们可以使用@ControllerAdvice来声明一些全局性的东西,最常见的是结合@ExceptionHandler注解用于全局异常的处理。
@ControllerAdvice是在类上声明的注解,其用法主要有三点:
- @ExceptionHandler注解标注的方法:用于捕获Controller中抛出的不同类型的异常,从而达到异常全局处理的目的;
- @InitBinder注解标注的方法:用于请求中注册自定义参数的解析,从而达到自定义请求参数格式的目的;
- @ModelAttribute注解标注的方法:表示此方法会在执行目标Controller方法之前执行 。
看下具体用法:
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 | // 这里@RestControllerAdvice等同于@ControllerAdvice + @ResponseBody @RestControllerAdvice public class GlobalHandler { private final Logger logger = LoggerFactory.getLogger(GlobalHandler. class ); // 这里@ModelAttribute("loginUserInfo")标注的modelAttribute()方法表示会在Controller方法之前 // 执行,返回当前登录用户的UserDetails对象 @ModelAttribute ( "loginUserInfo" ) public UserDetails modelAttribute() { return (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); } // @InitBinder标注的initBinder()方法表示注册一个Date类型的类型转换器,用于将类似这样的2019-06-10 // 日期格式的字符串转换成Date对象 @InitBinder protected void initBinder(WebDataBinder binder) { SimpleDateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd" ); dateFormat.setLenient( false ); binder.registerCustomEditor(Date. class , new CustomDateEditor(dateFormat, false )); } // 这里表示Controller抛出的MethodArgumentNotValidException异常由这个方法处理 @ExceptionHandler (MethodArgumentNotValidException. class ) public Result exceptionHandler(MethodArgumentNotValidException e) { Result result = new Result(BizExceptionEnum.INVALID_REQ_PARAM.getErrorCode(), BizExceptionEnum.INVALID_REQ_PARAM.getErrorMsg()); logger.error( "req params error" , e); return result; } // 这里表示Controller抛出的BizException异常由这个方法处理 @ExceptionHandler (BizException. class ) public Result exceptionHandler(BizException e) { BizExceptionEnum exceptionEnum = e.getBizExceptionEnum(); Result result = new Result(exceptionEnum.getErrorCode(), exceptionEnum.getErrorMsg()); logger.error( "business error" , e); return result; } // 这里就是通用的异常处理器了,所有预料之外的Exception异常都由这里处理 @ExceptionHandler (Exception. class ) public Result exceptionHandler(Exception e) { Result result = new Result( 1000 , "网络繁忙,请稍后再试" ); logger.error( "application error" , e); return result; } } |
@ExceptionHandler标注的多个方法分别表示只处理特定的异常。这里需要注意的是当Controller抛出的某个异常多个@ExceptionHandler标注的方法都适用时,Spring会选择最具体的异常处理方法来处理,也就是说@ExceptionHandler(Exception.class)这里标注的方法优先级最低,只有当其它方法都不适用时,才会来到这里处理。
下面我们看看Spring是怎么实现的,首先前端控制器DispatcherServlet对象在创建时会初始化一系列的对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | public class DispatcherServlet extends FrameworkServlet { // ...... protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); } // ...... } |
对于@ControllerAdvice 注解,我们重点关注initHandlerAdapters(context)和initHandlerExceptionResolvers(context)这两个方法。
initHandlerAdapters(context)方法会取得所有实现了HandlerAdapter接口的bean并保存起来,其中就有一个类型为RequestMappingHandlerAdapter的bean,这个bean就是@RequestMapping注解能起作用的关键,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:
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 | public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean { // ...... private void initControllerAdviceCache() { // ...... List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); List<Object> requestResponseBodyAdviceBeans = new ArrayList<>(); for (ControllerAdviceBean adviceBean : adviceBeans) { Class<?> beanType = adviceBean.getBeanType(); if (beanType == null ) { throw new IllegalStateException( "Unresolvable type for ControllerAdviceBean: " + adviceBean); } // 找到所有ModelAttribute标注的方法并缓存起来 Set<Method> attrMethods = MethodIntrospector.selectMethods(beanType, MODEL_ATTRIBUTE_METHODS); if (!attrMethods.isEmpty()) { this .modelAttributeAdviceCache.put(adviceBean, attrMethods); if (logger.isInfoEnabled()) { logger.info( "Detected @ModelAttribute methods in " + adviceBean); } } // 找到所有InitBinder标注的方法并缓存起来 Set<Method> binderMethods = MethodIntrospector.selectMethods(beanType, INIT_BINDER_METHODS); if (!binderMethods.isEmpty()) { this .initBinderAdviceCache.put(adviceBean, binderMethods); if (logger.isInfoEnabled()) { logger.info( "Detected @InitBinder methods in " + adviceBean); } } // ...... } } // ...... } |
来看DispatcherServlet的initHandlerExceptionResolvers(context)方法,方法会取得所有实现了HandlerExceptionResolver接口的bean并保存起来,其中就有一个类型为ExceptionHandlerExceptionResolver的bean,这个bean在应用启动过程中会获取所有被@ControllerAdvice注解标注的bean对象做进一步处理,关键代码在这里:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { // ...... private void initExceptionHandlerAdviceCache() { // ...... List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) { ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType()); if (resolver.hasExceptionMappings()) { // 找到所有ExceptionHandler标注的方法并保存成一个ExceptionHandlerMethodResolver类型的对象缓存起来 this .exceptionHandlerAdviceCache.put(adviceBean, resolver); if (logger.isInfoEnabled()) { logger.info( "Detected @ExceptionHandler methods in " + adviceBean); } } // ...... } } // ...... } |
当Controller抛出异常时,DispatcherServlet通过ExceptionHandlerExceptionResolver来解析异常,而ExceptionHandlerExceptionResolver又通过ExceptionHandlerMethodResolver 来解析异常, ExceptionHandlerMethodResolver 最终解析异常找到适用的@ExceptionHandler标注的方法是这里:
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 | public class ExceptionHandlerMethodResolver { // ...... private Method getMappedMethod(Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList<Class<? extends Throwable>>(); // 找到所有适用于Controller抛出异常的处理方法,例如Controller抛出的异常 // 是BizException(继承自RuntimeException),那么@ExceptionHandler(BizException.class)和 // @ExceptionHandler(Exception.class)标注的方法都适用此异常 for (Class<? extends Throwable> mappedException : this .mappedMethods.keySet()) { if (mappedException.isAssignableFrom(exceptionType)) { matches.add(mappedException); } } if (!matches.isEmpty()) { // 这里通过排序找到最适用的方法,排序的规则依据抛出异常相对于声明异常的深度,例如 // Controller抛出的异常是BizException(继承自RuntimeException),那么BizException // 相对于@ExceptionHandler(BizException.class)声明的BizException.class其深度是0, // 相对于@ExceptionHandler(Exception.class)声明的Exception.class其深度是2,所以 // @ExceptionHandler(BizException.class)标注的方法会排在前面 Collections.sort(matches, new ExceptionDepthComparator(exceptionType)); return this .mappedMethods.get(matches.get( 0 )); } else { return null ; } } // ...... } |
整个@ControllerAdvice处理的流程就是这样,这个设计还是非常灵活的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决