SpringMVC除了对请求URL的路由处理特别方便外,还支持对异常的统一处理机制,可以对业务操作时抛出的异常,unchecked异常以及状态码的异常进行统一处理。SpringMVC既提供简单的配置类,也提供了细粒度的异常控制机制。

SpringMVC中所有的异常处理通过接口HandlerExceptionResolver来实现,接口中只定义了一个方法

  1. public interface HandlerExceptionResolver {
  2. ModelAndView resolveException(
  3. HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex);
  4. }

方法中接受request和response信息,以及当前的处理Handler,和抛出的异常对象。并且提供抽象类AbstractHandlerExceptionResolver,实现resolveException方法,支持前置判断和处理,将实际处理抽象出doResolveException方法由子类来实现。

  1. @ControllerAdvice
  2. public class ExceptionAdvice {
  3. @ExceptionHandler({ArrayIndexOutOfBoundsException.class})
  4. @ResponseBody
  5. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  6. public ResponseDTO handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) {
  7. // TODO 记录log日志
  8. e.printStackTrace();
  9. ResponseDTO responseDTO = new ResponseDTO();
  10. responseDTO.wrapResponse(ServiceCodeEnum.E999997, "数组越界异常");
  11. return responseDTO;
  12. }
  13. @ExceptionHandler(value = ParamException.class)
  14. @ResponseBody
  15. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  16. public ResponseDTO handleParamException(ParamException e) {
  17. // TODO 记录log日志
  18. e.printStackTrace();
  19. ResponseDTO responseDTO = new ResponseDTO();
  20. responseDTO.wrapResponse(ServiceCodeEnum.E999998, "输入参数错误");
  21. return responseDTO;
  22. }
  23. @ExceptionHandler({Exception.class})
  24. @ResponseBody
  25. @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
  26. public ResponseDTO handleException(Exception e) {
  27. // TODO 记录log日志
  28. e.printStackTrace();
  29. ResponseDTO responseDTO = new ResponseDTO();
  30. responseDTO.wrapResponse(ServiceCodeEnum.E999999, "未知异常");
  31. return responseDTO;
  32. }
  33. }

我们看看 @ControllerAdvice

  1. @Target({ElementType.TYPE})
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Component
  5. public @interface ControllerAdvice {
  6. @AliasFor("basePackages")
  7. String[] value() default {};
  8. @AliasFor("value")
  9. String[] basePackages() default {};
  10. Class<?>[] basePackageClasses() default {};
  11. Class<?>[] assignableTypes() default {};
  12. Class<? extends Annotation>[] annotations() default {};
  13. }
  1. ControllerAdvice @Component 修饰,则说明标记 @ControllerAdvice 会被扫描到容器中

Spring mvc 的配置如下(这里用到了mvc:annotation-driven):

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core"
  4. xmlns:p="http://cxf.apache.org/policy" xmlns:ss="http://www.springframework.org/schema/security"
  5. xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jee="http://www.springframework.org/schema/jee"
  6. xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
  7. xmlns:context="http://www.springframework.org/schema/context"
  8. xmlns:mvc="http://www.springframework.org/schema/mvc"
  9. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  10. http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd
  11. http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
  12. http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd
  13. http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
  14. http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd
  15. http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd
  16. http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
  17. http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
  18. http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  19. http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
  20.  
  21. <context:component-scan
  22. base-package="frame.web.controller;frame.web.advice" />
  23.  
  24. <!--===================== view resovler ===================== -->
  25. <bean id="jstlViewResolver"
  26. class="org.springframework.web.servlet.view.UrlBasedViewResolver">
  27. <property name="order" value="1" />
  28. <property name="viewClass"
  29. value="org.springframework.web.servlet.view.JstlView" />
  30. <property name="prefix" value="/WEB-INF/jsp/" />
  31. </bean>
  32.  
  33. <mvc:annotation-driven/>
  34.  
  35.  
  36. <!-- 自定义参数转换 -->
  37. <bean id="conversionService"
  38. class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
  39. </bean>
  40.  
  41. </beans>
AnnotationDrivenBeanDefinitionParser类就是用于解析<mvc:annotation-drive>标签的。下面是AnnotationDrivenBeanDefinitionParser的部分源码:
  1. package org.springframework.web.servlet.config;
  2. class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser {
  3. /**
  4. *parse是这个类的核心方法,它用于解析 annotation-drive标签里的内容,根据标签里的内容往spring ioc容器里注入具体的对象。
  5. **/
  6. @Override
  7. public BeanDefinition parse(Element element, ParserContext parserContext) {
  8. Object source = parserContext.extractSource(element);
  9. XmlReaderContext readerContext = parserContext.getReaderContext();
  10. CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source);
  11. parserContext.pushContainingComponent(compDefinition);
  12. RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext);
  13. //这里有我们熟悉的RequestMappingHandlerMapping,
  14. RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class);
  15. handlerMappingDef.setSource(source);
  16. handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  17. handlerMappingDef.getPropertyValues().add("order", 0);
  18. handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
  19. if (element.hasAttribute("enable-matrix-variables")) {
  20. Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables"));
  21. handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
  22. }
  23. else if (element.hasAttribute("enableMatrixVariables")) {
  24. Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables"));
  25. handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables);
  26. }
  27. configurePathMatchingProperties(handlerMappingDef, element, parserContext);
  28. readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef);
  29. RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source);
  30. handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef);
  31. //这里会注入具体的ConversionService用于将json,xml转成Spring mvc里的请求和返回对象
  32. RuntimeBeanReference conversionService = getConversionService(element, source, parserContext);
  33. RuntimeBeanReference validator = getValidator(element, source, parserContext);
  34. RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element);
  35. RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class);
  36. bindingDef.setSource(source);
  37. bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  38. bindingDef.getPropertyValues().add("conversionService", conversionService);
  39. bindingDef.getPropertyValues().add("validator", validator);
  40. bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver);
  41. ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext);
  42. ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext);
  43. ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext);
  44. String asyncTimeout = getAsyncTimeout(element);
  45. RuntimeBeanReference asyncExecutor = getAsyncExecutor(element);
  46. ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext);
  47. ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext);
  48. //RequestMappingHandlerAdapter也会在这里注入
  49. RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class);
  50. handlerAdapterDef.setSource(source);
  51. handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  52. handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
  53. handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef);
  54. handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters);
  55. addRequestBodyAdvice(handlerAdapterDef);
  56. addResponseBodyAdvice(handlerAdapterDef);
  57. if (element.hasAttribute("ignore-default-model-on-redirect")) {
  58. Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect"));
  59. handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
  60. }
  61. else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) {
  62. // "ignoreDefaultModelOnRedirect" spelling is deprecated
  63. Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));
  64. handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);
  65. }
  66. if (argumentResolvers != null) {
  67. handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
  68. }
  69. if (returnValueHandlers != null) {
  70. handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
  71. }
  72. if (asyncTimeout != null) {
  73. handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout);
  74. }
  75. if (asyncExecutor != null) {
  76. handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor);
  77. }
  78. handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors);
  79. handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors);
  80. readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef);
  81. String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME;
  82. RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class);
  83. uriCompContribDef.setSource(source);
  84. uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef);
  85. uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService);
  86. readerContext.getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef);
  87. RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class);
  88. csInterceptorDef.setSource(source);
  89. csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService);
  90. RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
  91. mappedCsInterceptorDef.setSource(source);
  92. mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  93. mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null);
  94. mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef);
  95. String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedCsInterceptorDef);
  96. //这里有我们需要找的ExceptionHandlerExceptionResolver,
  97. RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class);
  98. exceptionHandlerExceptionResolver.setSource(source);
  99. exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  100. exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager);
  101. exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters);
  102. exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0);
  103. addResponseBodyAdvice(exceptionHandlerExceptionResolver);
  104. if (argumentResolvers != null) {
  105. exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers);
  106. }
  107. if (returnValueHandlers != null) {
  108. exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers);
  109. }
  110. String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver);
  111. RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class);
  112. responseStatusExceptionResolver.setSource(source);
  113. responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  114. responseStatusExceptionResolver.getPropertyValues().add("order", 1);
  115. String responseStatusExceptionResolverName =
  116. readerContext.registerWithGeneratedName(responseStatusExceptionResolver);
  117. RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class);
  118. defaultExceptionResolver.setSource(source);
  119. defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  120. defaultExceptionResolver.getPropertyValues().add("order", 2);
  121. String defaultExceptionResolverName =
  122. readerContext.registerWithGeneratedName(defaultExceptionResolver);
  123. parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
  124. parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME));
  125. parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName));
  126. parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName));
  127. parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName));
  128. parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName));
  129. parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName));
  130. // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off"
  131. MvcNamespaceUtils.registerDefaultComponents(parserContext, source);
  132. parserContext.popAndRegisterContainingComponent();
  133. return null;
  134. }
  135. }
通过上面代码的分析, 我们可以找到ExceptionHandlerExceptionResolver这个类来用于处理Spring MVC的各种异常,那ExceptionHandlerExceptionResolver具体又是如何跟ControllerAdvice配合使用来处理各种异常的呢?我们来看看ExceptionHandlerExceptionResolver里的关键代码:
  1. package org.springframework.web.servlet.mvc.method.annotation;
  2. //我们考到这个类实现了InitializingBean,则容器初始化的时候在实例化此Bean后会调用afterPropertiesSet()
  3. public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
  4. implements ApplicationContextAware, InitializingBean {
  5. //这里有个map用于保存ControllerAdviceBean和ExceptionHandlerMethodResolver
  6. private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =
  7. new LinkedHashMap<ControllerAdviceBean, ExceptionHandlerMethodResolver>();
  8. //这个方法是由spring 容器调用的
  9. @Override
  10. public void afterPropertiesSet() {
  11. // Do this first, it may add ResponseBodyAdvice beans
  12. //这个方法里会处理ExceptionHandler
  13. initExceptionHandlerAdviceCache();
  14. if (this.argumentResolvers == null) {
  15. List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
  16. this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
  17. }
  18. if (this.returnValueHandlers == null) {
  19. List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
  20. this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
  21. }
  22. }
  23. /**
  24. *这个方法里会在spring ioc容器里找出标注了@ControllerAdvice的类,如果有方法标注了@ExceptionHandler会生成一个ExceptionHandlerMethodResolver类用于处理异常并放到exceptionHandlerAdviceCache这个map缓存类里。
  25. **/
  26. private void initExceptionHandlerAdviceCache() {
  27. if (getApplicationContext() == null) {
  28. return;
  29. }
  30. if (logger.isDebugEnabled()) {
  31. logger.debug("Looking for exception mappings: " + getApplicationContext());
  32. }
  33. //这里会找到容器里标注了@ControllerAdvice注解的类
  34. List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
  35. AnnotationAwareOrderComparator.sort(adviceBeans);
  36. for (ControllerAdviceBean adviceBean : adviceBeans) {
  37. //这个构造方法里会检查ControllerAdvice类里是否有@ExceptionHandler标注的方法,在ExceptionHandlerMethodResolver 有个异常的map。
  38. //在ExceptionHandlerMethodResolver构造器中会通过反射拿到所有标注@ExceptionHandler的方法并加入ExceptionHandlerMethodResolver的map中
  39. //key为 @ExceptionHandler(value = ParamException.class) 标注的value,这里就是ParamException.class,值为标注@ExceptionHandler的Method
  40. ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType());
  41. if (resolver.hasExceptionMappings()) {
  42. //如果有@ExceptionHandler方法,会执行下面的逻辑
  43. //将标注@ControllerAdvice的类Bean,和此Bean中封装了所有Exception为key,Method为value的Map的ExceptionHandlerMethodResolver对象加入到exceptionHandlerAdviceCache的缓存中
  44. this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
  45. if (logger.isInfoEnabled()) {
  46. logger.info("Detected @ExceptionHandler methods in " + adviceBean);
  47. }
  48. }
  49. if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) {
  50. this.responseBodyAdvice.add(adviceBean);
  51. if (logger.isInfoEnabled()) {
  52. logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean);
  53. }
  54. }
  55. }
  56. }
  57. /**
  58. ** 这个方法会根据exceptionHandlerAdviceCache这个找到具体需要处理异常的方法,这个后面再讲
  59. */
  60. protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
  61. Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
  62. if (handlerMethod != null) {
  63. ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
  64. if (resolver == null) {
  65. resolver = new ExceptionHandlerMethodResolver(handlerType);
  66. this.exceptionHandlerCache.put(handlerType, resolver);
  67. }
  68. Method method = resolver.resolveMethod(exception);
  69. if (method != null) {
  70. return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
  71. }
  72. }
  73. for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
  74. if (entry.getKey().isApplicableToBeanType(handlerType)) {
  75. ExceptionHandlerMethodResolver resolver = entry.getValue();
  76. //根据具体的异常找到处理异常的方法,然后调用
  77. Method method = resolver.resolveMethod(exception);
  78. if (method != null) {
  79. return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
  80. }
  81. }
  82. }
  83. return null;
  84. }
  85. }

我们来看看 ExceptionHandlerMethodResolver这个类

  1. public class ExceptionHandlerMethodResolver {
  2. public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {
  3. public boolean matches(Method method) {
  4. return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null;
  5. }
  6. };
  7. private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", new Class[0]);
  8. //此缓存Map存放了@ControllerAdvice中所有注解了@ExceptionHandler的方法,其中@ExceptionHandler的value也就是Exception做为Key,值为当前Method
  9. private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap(16);
  10. private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentHashMap(16);
  11. public ExceptionHandlerMethodResolver(Class<?> handlerType) {
  12. //通过反射拿到当前Class的所有方法,也就是标注了@ControllerAdvice的所有方法
  13. Iterator var2 = MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS).iterator();
  14. //遍历所有的方法,寻找标注了@ExceptionHandler的方法
  15. while(var2.hasNext()) {
  16. Method method = (Method)var2.next();
  17. //这里获取到标注了@ExceptionHandler的方法上所有的@ExceptionHandler中的value
  18. //如{ArrayIndexOutOfBoundsException.class,ParamException.calss}
  19. Iterator var4 = this.detectExceptionMappings(method).iterator();
  20. while(var4.hasNext()) {
  21. Class<? extends Throwable> exceptionType = (Class)var4.next();
  22. //将ArrayIndexOutOfBoundsException.class作为key,method做为value加入到map缓存中
  23. this.addExceptionMapping(exceptionType, method);
  24. }
  25. }
  26. }
  27. private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
  28. List<Class<? extends Throwable>> result = new ArrayList();
  29. //这里获取到标注了@ExceptionHandler的方法上所有的@ExceptionHandler中的value
  30. //如{ArrayIndexOutOfBoundsException.class,ParamException.calss}
  31. this.detectAnnotationExceptionMappings(method, result);
  32. if (result.isEmpty()) {
  33. Class[] var3 = method.getParameterTypes();
  34. int var4 = var3.length;
  35. for(int var5 = 0; var5 < var4; ++var5) {
  36. Class<?> paramType = var3[var5];
  37. if (Throwable.class.isAssignableFrom(paramType)) {
  38. result.add(paramType);
  39. }
  40. }
  41. }
  42. Assert.notEmpty(result, "No exception types mapped to {" + method + "}");
  43. return result;
  44. }
  45. protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
  46. //判断此方法是否标记@ExceptionHandler,如果没有则返回null,如果有标记则返回ExceptionHandler
  47. ExceptionHandler ann = (ExceptionHandler)AnnotationUtils.findAnnotation(method, ExceptionHandler.class);
  48. //获取ExceptionHandler注解的所有值,这里是一个数组,有可能有多个,如@ExceptionHandler({ArrayIndexOutOfBoundsException.class,ParamException.calss})
  49. result.addAll(Arrays.asList(ann.value()));
  50. }
  51. }

SpringMVC怎么在请求处理的过程中完成对异常的统一处理的呢?我们从源码来深度解读。

回到DispatcherServlet的doDispatcher方法

  1. try {
  2. processedRequest = checkMultipart(request);
  3. multipartRequestParsed = (processedRequest != request);
  4. // Determine handler for the current request.
  5. mappedHandler = getHandler(processedRequest);
  6. if (mappedHandler == null || mappedHandler.getHandler() == null) {
  7. noHandlerFound(processedRequest, response);
  8. return;
  9. }
  10. // Determine handler adapter for the current request.
  11. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
  12. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  13. return;
  14. }
  15. // Actually invoke the handler.
  16. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  17. if (asyncManager.isConcurrentHandlingStarted()) {
  18. return;
  19. }
  20. applyDefaultViewName(processedRequest, mv);
  21. mappedHandler.applyPostHandle(processedRequest, response, mv);
  22. }
  23. catch (Exception ex) {
  24. dispatchException = ex;
  25. }
  26. catch (Throwable err) {
  27. dispatchException = new NestedServletException("Handler dispatch failed", err);
  28. }
  29. processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

可以看到对请求处理的核心处理使用一个大的try/catch,如果出现异常,统一封装成dispatchException交给processDispatchResult方法进行处理。我们知道processDispatchResult方法用来对返回视图进行操作,而同时也对异常进行统一处理。

在processDispatchResult中,首先对异常进行判断。

  1. if (exception != null) {
  2. if (exception instanceof ModelAndViewDefiningException) {
  3. logger.debug("ModelAndViewDefiningException encountered", exception);
  4. mv = ((ModelAndViewDefiningException) exception).getModelAndView();
  5. }
  6. else {
  7. Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
  8. mv = processHandlerException(request, response, handler, exception);
  9. errorView = (mv != null);
  10. }
  11. }

如果不是特殊的ModelAndViewDefiningException,则由processHandlerException来操作。

  1. protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
  2. Object handler, Exception ex) throws Exception {
  3. // Check registered HandlerExceptionResolvers...
  4. ModelAndView exMv = null;
  5. // 遍历所有注册的异常处理器,由异常处理器进行处理
  6. for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) {
  7. exMv = handlerExceptionResolver.resolveException(request, response, handler, ex);
  8. if (exMv != null) {
  9. break;
  10. }
  11. }
  12. // 如果异常视图存在,则转向异常视图
  13. if (exMv != null) {
  14. if (exMv.isEmpty()) {
  15. request.setAttribute(EXCEPTION_ATTRIBUTE, ex);
  16. return null;
  17. }
  18. // We might still need view name translation for a plain error model...
  19. if (!exMv.hasView()) {
  20. exMv.setViewName(getDefaultViewName(request));
  21. }
  22. if (logger.isDebugEnabled()) {
  23. logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex);
  24. }
  25. WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
  26. return exMv;
  27. }
  28. throw ex;
  29. }

我们主要关注异常处理器对异常的处理,SpringMVC通过HandlerExceptionResolver的resolveException调用实现类的实际实现方法doResolveException。

ExceptionHandlerExceptionResolver支持了@ExceptionHandler注解的实现。它的抽象基类AbstractHandlerMethodExceptionResolver继承了AbstractHandlerExceptionResolver,doResolveException方法实际调用ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法。

  1. protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
  2. return this.doResolveHandlerMethodException(request, response, (HandlerMethod)handler, ex);
  3. }
  4. protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
  5. HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {
  6. // 根据HandlerMethod和exception获取异常处理的Method
  7. ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
  8. if (exceptionHandlerMethod == null) {
  9. return null;
  10. }
  11. // 设置异常处理方法的参数解析器和返回值解析器
  12. exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
  13. exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
  14. ServletWebRequest webRequest = new ServletWebRequest(request, response);
  15. ModelAndViewContainer mavContainer = new ModelAndViewContainer();
  16. // 执行异常处理方法
  17. try {
  18. if (logger.isDebugEnabled()) {
  19. logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
  20. }
  21. Throwable cause = exception.getCause();
  22. if (cause != null) {
  23. // Expose cause as provided argument as well
  24. exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
  25. }
  26. else {
  27. // Otherwise, just the given exception as-is
  28. exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
  29. }
  30. }
  31. catch (Throwable invocationEx) {
  32. // Any other than the original exception is unintended here,
  33. // probably an accident (e.g. failed assertion or the like).
  34. if (invocationEx != exception && logger.isWarnEnabled()) {
  35. logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);
  36. }
  37. // Continue with default processing of the original exception...
  38. return null;
  39. }
  40. // 对返回的视图模型进行处理
  41. if (mavContainer.isRequestHandled()) {
  42. return new ModelAndView();
  43. }
  44. else {
  45. ModelMap model = mavContainer.getModel();
  46. HttpStatus status = mavContainer.getStatus();
  47. ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
  48. mav.setViewName(mavContainer.getViewName());
  49. if (!mavContainer.isViewReference()) {
  50. mav.setView((View) mavContainer.getView());
  51. }
  52. if (model instanceof RedirectAttributes) {
  53. Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
  54. request = webRequest.getNativeRequest(HttpServletRequest.class);
  55. RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
  56. }
  57. return mav;
  58. }
  59. }

我们主要关注的是如何匹配到异常处理方法的,也就是 ExceptionHandlerExceptionResolver中的 getExceptionHandlerMethod

  1. protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
  2. Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null);
  3. // 从当前Controller中匹配异常处理Method,此处我们暂时不分析
  4. if (handlerMethod != null) {
  5. ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
  6. if (resolver == null) {
  7. resolver = new ExceptionHandlerMethodResolver(handlerType);
  8. this.exceptionHandlerCache.put(handlerType, resolver);
  9. }
  10. Method method = resolver.resolveMethod(exception);
  11. if (method != null) {
  12. return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method);
  13. }
  14. }
  15. // 从ControllerAdvice中匹配异常处理Method
  16. // 我们知道容器初始化的时候,已经寻找所有标注了@ControllerAdvice的类,并将此类标注了@ExceptionHandler的方法放到当前类的exceptionHandlerAdviceCache中
  17. //遍历所有的@ControllerAdvice生成的缓存
  18. for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
  19. if (entry.getKey().isApplicableToBeanType(handlerType)) {
  20. //拿到ExceptionHandlerMethodResolver,这个对象里包含了标注为@ExceptionHandler的Key为Excpthion,value为Method的缓存Map
  21. ExceptionHandlerMethodResolver resolver = entry.getValue();
  22. //寻找ExceptionHandlerMethodResolver有没有标注@ExceptionHandler能匹配当前异常的方法
  23. Method method = resolver.resolveMethod(exception);
  24. if (method != null) {
  25. return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);
  26. }
  27. }
  28. }
  29. return null;
  30. }
  31. public Method resolveMethod(Exception exception) {
  32. Method method = this.resolveMethodByExceptionType(exception.getClass());
  33. if (method == null) {
  34. Throwable cause = exception.getCause();
  35. if (cause != null) {
  36. method = this.resolveMethodByExceptionType(cause.getClass());
  37. }
  38. }
  39. return method;
  40. }
  41. public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
  42. //先从exceptionLookupCache缓存中拿,第一次肯定拿不到,因为我们是存在mappedMethods这个缓存中
  43. Method method = (Method)this.exceptionLookupCache.get(exceptionType);
  44. if (method == null) {
  45. method = this.getMappedMethod(exceptionType);
  46. this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND);
  47. }
  48. return method != NO_METHOD_FOUND ? method : null;
  49. }
  50. private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
  51. List<Class<? extends Throwable>> matches = new ArrayList();
  52. //拿到所有的Key
  53. Iterator var3 = this.mappedMethods.keySet().iterator();
  54. while(var3.hasNext()) {
  55. Class<? extends Throwable> mappedException = (Class)var3.next();
  56. //判断exceptionType是不是mappedException本身或者其子类
  57. if (mappedException.isAssignableFrom(exceptionType)) {
  58. matches.add(mappedException);
  59. }
  60. }
  61. if (!matches.isEmpty()) {
  62. Collections.sort(matches, new ExceptionDepthComparator(exceptionType));
  63. //返回匹配到异常的Method
  64. return (Method)this.mappedMethods.get(matches.get(0));
  65. } else {
  66. return null;
  67. }
  68. }

匹配到exceptionHandlerMethod后,设置一些方法执行的环境,然后调用ServletInvocableHandlerMethod中的invokeAndHandle去执行,这个调用过程和正常请求的调用就是一致了。

  1. exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, new Object[]{exception, cause, handlerMethod});

这里就是调用异常处理的方法,总体来说,就是SpringMvc启动的时候初始化异常处理的组件,将 @ControllerAdvice标记的特殊类和@ExceptionHandler 标记的方法存入缓存中,当目标Controller出现异常的时候,就通过抛出的异常在缓存中找到对应的处理方法,然后去调用对应的异常处理方法就OK了。

 

版权声明:本文为java-chen-hao原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/java-chen-hao/p/11190659.html