SpringMVC除了对请求URL的路由处理特别方便外,还支持对异常的统一处理机制,可以对业务操作时抛出的异常,unchecked异常以及状态码的异常进行统一处理。SpringMVC既提供简单的配置类,也提供了细粒度的异常控制机制。
SpringMVC中所有的异常处理通过接口HandlerExceptionResolver来实现,接口中只定义了一个方法
public interface HandlerExceptionResolver { ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex); }
方法中接受request和response信息,以及当前的处理Handler,和抛出的异常对象。并且提供抽象类AbstractHandlerExceptionResolver,实现resolveException方法,支持前置判断和处理,将实际处理抽象出doResolveException方法由子类来实现。
@ControllerAdvice和@ExceptionHandler的简单使用
@ControllerAdvice public class ExceptionAdvice { @ExceptionHandler({ArrayIndexOutOfBoundsException.class}) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseDTO handleArrayIndexOutOfBoundsException(ArrayIndexOutOfBoundsException e) { // TODO 记录log日志 e.printStackTrace(); ResponseDTO responseDTO = new ResponseDTO(); responseDTO.wrapResponse(ServiceCodeEnum.E999997, "数组越界异常"); return responseDTO; } @ExceptionHandler(value = ParamException.class) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseDTO handleParamException(ParamException e) { // TODO 记录log日志 e.printStackTrace(); ResponseDTO responseDTO = new ResponseDTO(); responseDTO.wrapResponse(ServiceCodeEnum.E999998, "输入参数错误"); return responseDTO; } @ExceptionHandler({Exception.class}) @ResponseBody @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) public ResponseDTO handleException(Exception e) { // TODO 记录log日志 e.printStackTrace(); ResponseDTO responseDTO = new ResponseDTO(); responseDTO.wrapResponse(ServiceCodeEnum.E999999, "未知异常"); return responseDTO; } }
我们看看 @ControllerAdvice
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component public @interface ControllerAdvice { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; Class<?>[] assignableTypes() default {}; Class<? extends Annotation>[] annotations() default {}; }
ControllerAdvice 被 @Component 修饰,则说明标记 @ControllerAdvice 会被扫描到容器中
Spring mvc 的配置如下(这里用到了mvc:annotation-driven):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:cxf="http://cxf.apache.org/core" xmlns:p="http://cxf.apache.org/policy" xmlns:ss="http://www.springframework.org/schema/security" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd http://cxf.apache.org/policy http://cxf.apache.org/schemas/policy.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd http://cxf.apache.org/bindings/soap http://cxf.apache.org/schemas/configuration/soap.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"> <context:component-scan base-package="frame.web.controller;frame.web.advice" /> <!--===================== view resovler ===================== --> <bean id="jstlViewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver"> <property name="order" value="1" /> <property name="viewClass" value="org.springframework.web.servlet.view.JstlView" /> <property name="prefix" value="/WEB-INF/jsp/" /> </bean> <mvc:annotation-driven/> <!-- 自定义参数转换 --> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> </bean> </beans>
AnnotationDrivenBeanDefinitionParser类就是用于解析<mvc:annotation-drive>标签的。下面是AnnotationDrivenBeanDefinitionParser的部分源码:
package org.springframework.web.servlet.config; class AnnotationDrivenBeanDefinitionParser implements BeanDefinitionParser { /** *parse是这个类的核心方法,它用于解析 annotation-drive标签里的内容,根据标签里的内容往spring ioc容器里注入具体的对象。 **/ @Override public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); XmlReaderContext readerContext = parserContext.getReaderContext(); CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source); parserContext.pushContainingComponent(compDefinition); RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext); //这里有我们熟悉的RequestMappingHandlerMapping, RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class); handlerMappingDef.setSource(source); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.getPropertyValues().add("order", 0); handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); if (element.hasAttribute("enable-matrix-variables")) { Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enable-matrix-variables")); handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables); } else if (element.hasAttribute("enableMatrixVariables")) { Boolean enableMatrixVariables = Boolean.valueOf(element.getAttribute("enableMatrixVariables")); handlerMappingDef.getPropertyValues().add("removeSemicolonContent", !enableMatrixVariables); } configurePathMatchingProperties(handlerMappingDef, element, parserContext); readerContext.getRegistry().registerBeanDefinition(HANDLER_MAPPING_BEAN_NAME , handlerMappingDef); RuntimeBeanReference corsConfigurationsRef = MvcNamespaceUtils.registerCorsConfigurations(null, parserContext, source); handlerMappingDef.getPropertyValues().add("corsConfigurations", corsConfigurationsRef); //这里会注入具体的ConversionService用于将json,xml转成Spring mvc里的请求和返回对象 RuntimeBeanReference conversionService = getConversionService(element, source, parserContext); RuntimeBeanReference validator = getValidator(element, source, parserContext); RuntimeBeanReference messageCodesResolver = getMessageCodesResolver(element); RootBeanDefinition bindingDef = new RootBeanDefinition(ConfigurableWebBindingInitializer.class); bindingDef.setSource(source); bindingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); bindingDef.getPropertyValues().add("conversionService", conversionService); bindingDef.getPropertyValues().add("validator", validator); bindingDef.getPropertyValues().add("messageCodesResolver", messageCodesResolver); ManagedList<?> messageConverters = getMessageConverters(element, source, parserContext); ManagedList<?> argumentResolvers = getArgumentResolvers(element, parserContext); ManagedList<?> returnValueHandlers = getReturnValueHandlers(element, parserContext); String asyncTimeout = getAsyncTimeout(element); RuntimeBeanReference asyncExecutor = getAsyncExecutor(element); ManagedList<?> callableInterceptors = getCallableInterceptors(element, source, parserContext); ManagedList<?> deferredResultInterceptors = getDeferredResultInterceptors(element, source, parserContext); //RequestMappingHandlerAdapter也会在这里注入 RootBeanDefinition handlerAdapterDef = new RootBeanDefinition(RequestMappingHandlerAdapter.class); handlerAdapterDef.setSource(source); handlerAdapterDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerAdapterDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); handlerAdapterDef.getPropertyValues().add("webBindingInitializer", bindingDef); handlerAdapterDef.getPropertyValues().add("messageConverters", messageConverters); addRequestBodyAdvice(handlerAdapterDef); addResponseBodyAdvice(handlerAdapterDef); if (element.hasAttribute("ignore-default-model-on-redirect")) { Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignore-default-model-on-redirect")); handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel); } else if (element.hasAttribute("ignoreDefaultModelOnRedirect")) { // "ignoreDefaultModelOnRedirect" spelling is deprecated Boolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect")); handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel); } if (argumentResolvers != null) { handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers); } if (returnValueHandlers != null) { handlerAdapterDef.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers); } if (asyncTimeout != null) { handlerAdapterDef.getPropertyValues().add("asyncRequestTimeout", asyncTimeout); } if (asyncExecutor != null) { handlerAdapterDef.getPropertyValues().add("taskExecutor", asyncExecutor); } handlerAdapterDef.getPropertyValues().add("callableInterceptors", callableInterceptors); handlerAdapterDef.getPropertyValues().add("deferredResultInterceptors", deferredResultInterceptors); readerContext.getRegistry().registerBeanDefinition(HANDLER_ADAPTER_BEAN_NAME , handlerAdapterDef); String uriCompContribName = MvcUriComponentsBuilder.MVC_URI_COMPONENTS_CONTRIBUTOR_BEAN_NAME; RootBeanDefinition uriCompContribDef = new RootBeanDefinition(CompositeUriComponentsContributorFactoryBean.class); uriCompContribDef.setSource(source); uriCompContribDef.getPropertyValues().addPropertyValue("handlerAdapter", handlerAdapterDef); uriCompContribDef.getPropertyValues().addPropertyValue("conversionService", conversionService); readerContext.getRegistry().registerBeanDefinition(uriCompContribName, uriCompContribDef); RootBeanDefinition csInterceptorDef = new RootBeanDefinition(ConversionServiceExposingInterceptor.class); csInterceptorDef.setSource(source); csInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, conversionService); RootBeanDefinition mappedCsInterceptorDef = new RootBeanDefinition(MappedInterceptor.class); mappedCsInterceptorDef.setSource(source); mappedCsInterceptorDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(0, (Object) null); mappedCsInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, csInterceptorDef); String mappedInterceptorName = readerContext.registerWithGeneratedName(mappedCsInterceptorDef); //这里有我们需要找的ExceptionHandlerExceptionResolver, RootBeanDefinition exceptionHandlerExceptionResolver = new RootBeanDefinition(ExceptionHandlerExceptionResolver.class); exceptionHandlerExceptionResolver.setSource(source); exceptionHandlerExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); exceptionHandlerExceptionResolver.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); exceptionHandlerExceptionResolver.getPropertyValues().add("messageConverters", messageConverters); exceptionHandlerExceptionResolver.getPropertyValues().add("order", 0); addResponseBodyAdvice(exceptionHandlerExceptionResolver); if (argumentResolvers != null) { exceptionHandlerExceptionResolver.getPropertyValues().add("customArgumentResolvers", argumentResolvers); } if (returnValueHandlers != null) { exceptionHandlerExceptionResolver.getPropertyValues().add("customReturnValueHandlers", returnValueHandlers); } String methodExceptionResolverName = readerContext.registerWithGeneratedName(exceptionHandlerExceptionResolver); RootBeanDefinition responseStatusExceptionResolver = new RootBeanDefinition(ResponseStatusExceptionResolver.class); responseStatusExceptionResolver.setSource(source); responseStatusExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); responseStatusExceptionResolver.getPropertyValues().add("order", 1); String responseStatusExceptionResolverName = readerContext.registerWithGeneratedName(responseStatusExceptionResolver); RootBeanDefinition defaultExceptionResolver = new RootBeanDefinition(DefaultHandlerExceptionResolver.class); defaultExceptionResolver.setSource(source); defaultExceptionResolver.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); defaultExceptionResolver.getPropertyValues().add("order", 2); String defaultExceptionResolverName = readerContext.registerWithGeneratedName(defaultExceptionResolver); parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME)); parserContext.registerComponent(new BeanComponentDefinition(handlerAdapterDef, HANDLER_ADAPTER_BEAN_NAME)); parserContext.registerComponent(new BeanComponentDefinition(uriCompContribDef, uriCompContribName)); parserContext.registerComponent(new BeanComponentDefinition(exceptionHandlerExceptionResolver, methodExceptionResolverName)); parserContext.registerComponent(new BeanComponentDefinition(responseStatusExceptionResolver, responseStatusExceptionResolverName)); parserContext.registerComponent(new BeanComponentDefinition(defaultExceptionResolver, defaultExceptionResolverName)); parserContext.registerComponent(new BeanComponentDefinition(mappedCsInterceptorDef, mappedInterceptorName)); // Ensure BeanNameUrlHandlerMapping (SPR-8289) and default HandlerAdapters are not "turned off" MvcNamespaceUtils.registerDefaultComponents(parserContext, source); parserContext.popAndRegisterContainingComponent(); return null; } }
通过上面代码的分析, 我们可以找到ExceptionHandlerExceptionResolver这个类来用于处理Spring MVC的各种异常,那ExceptionHandlerExceptionResolver具体又是如何跟ControllerAdvice配合使用来处理各种异常的呢?我们来看看ExceptionHandlerExceptionResolver里的关键代码:
package org.springframework.web.servlet.mvc.method.annotation; //我们考到这个类实现了InitializingBean,则容器初始化的时候在实例化此Bean后会调用afterPropertiesSet() public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver implements ApplicationContextAware, InitializingBean { //这里有个map用于保存ControllerAdviceBean和ExceptionHandlerMethodResolver private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<ControllerAdviceBean, ExceptionHandlerMethodResolver>(); //这个方法是由spring 容器调用的 @Override public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans //这个方法里会处理ExceptionHandler initExceptionHandlerAdviceCache(); if (this.argumentResolvers == null) { List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } } /** *这个方法里会在spring ioc容器里找出标注了@ControllerAdvice的类,如果有方法标注了@ExceptionHandler会生成一个ExceptionHandlerMethodResolver类用于处理异常并放到exceptionHandlerAdviceCache这个map缓存类里。 **/ private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } if (logger.isDebugEnabled()) { logger.debug("Looking for exception mappings: " + getApplicationContext()); } //这里会找到容器里标注了@ControllerAdvice注解的类 List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); AnnotationAwareOrderComparator.sort(adviceBeans); for (ControllerAdviceBean adviceBean : adviceBeans) { //这个构造方法里会检查ControllerAdvice类里是否有@ExceptionHandler标注的方法,在ExceptionHandlerMethodResolver 有个异常的map。 //在ExceptionHandlerMethodResolver构造器中会通过反射拿到所有标注@ExceptionHandler的方法并加入ExceptionHandlerMethodResolver的map中 //key为 @ExceptionHandler(value = ParamException.class) 标注的value,这里就是ParamException.class,值为标注@ExceptionHandler的Method ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType()); if (resolver.hasExceptionMappings()) { //如果有@ExceptionHandler方法,会执行下面的逻辑 //将标注@ControllerAdvice的类Bean,和此Bean中封装了所有Exception为key,Method为value的Map的ExceptionHandlerMethodResolver对象加入到exceptionHandlerAdviceCache的缓存中 this.exceptionHandlerAdviceCache.put(adviceBean, resolver); if (logger.isInfoEnabled()) { logger.info("Detected @ExceptionHandler methods in " + adviceBean); } } if (ResponseBodyAdvice.class.isAssignableFrom(adviceBean.getBeanType())) { this.responseBodyAdvice.add(adviceBean); if (logger.isInfoEnabled()) { logger.info("Detected ResponseBodyAdvice implementation in " + adviceBean); } } } } /** ** 这个方法会根据exceptionHandlerAdviceCache这个找到具体需要处理异常的方法,这个后面再讲 */ protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); if (handlerMethod != null) { ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); } } for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { if (entry.getKey().isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); //根据具体的异常找到处理异常的方法,然后调用 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); } } } return null; } }
我们来看看 ExceptionHandlerMethodResolver这个类
public class ExceptionHandlerMethodResolver { public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() { public boolean matches(Method method) { return AnnotationUtils.findAnnotation(method, ExceptionHandler.class) != null; } }; private static final Method NO_METHOD_FOUND = ClassUtils.getMethodIfAvailable(System.class, "currentTimeMillis", new Class[0]); //此缓存Map存放了@ControllerAdvice中所有注解了@ExceptionHandler的方法,其中@ExceptionHandler的value也就是Exception做为Key,值为当前Method private final Map<Class<? extends Throwable>, Method> mappedMethods = new ConcurrentHashMap(16); private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentHashMap(16); public ExceptionHandlerMethodResolver(Class<?> handlerType) { //通过反射拿到当前Class的所有方法,也就是标注了@ControllerAdvice的所有方法 Iterator var2 = MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS).iterator(); //遍历所有的方法,寻找标注了@ExceptionHandler的方法 while(var2.hasNext()) { Method method = (Method)var2.next(); //这里获取到标注了@ExceptionHandler的方法上所有的@ExceptionHandler中的value //如{ArrayIndexOutOfBoundsException.class,ParamException.calss} Iterator var4 = this.detectExceptionMappings(method).iterator(); while(var4.hasNext()) { Class<? extends Throwable> exceptionType = (Class)var4.next(); //将ArrayIndexOutOfBoundsException.class作为key,method做为value加入到map缓存中 this.addExceptionMapping(exceptionType, method); } } } private List<Class<? extends Throwable>> detectExceptionMappings(Method method) { List<Class<? extends Throwable>> result = new ArrayList(); //这里获取到标注了@ExceptionHandler的方法上所有的@ExceptionHandler中的value //如{ArrayIndexOutOfBoundsException.class,ParamException.calss} this.detectAnnotationExceptionMappings(method, result); if (result.isEmpty()) { Class[] var3 = method.getParameterTypes(); int var4 = var3.length; for(int var5 = 0; var5 < var4; ++var5) { Class<?> paramType = var3[var5]; if (Throwable.class.isAssignableFrom(paramType)) { result.add(paramType); } } } Assert.notEmpty(result, "No exception types mapped to {" + method + "}"); return result; } protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) { //判断此方法是否标记@ExceptionHandler,如果没有则返回null,如果有标记则返回ExceptionHandler ExceptionHandler ann = (ExceptionHandler)AnnotationUtils.findAnnotation(method, ExceptionHandler.class); //获取ExceptionHandler注解的所有值,这里是一个数组,有可能有多个,如@ExceptionHandler({ArrayIndexOutOfBoundsException.class,ParamException.calss}) result.addAll(Arrays.asList(ann.value())); } }
异常处理原理
SpringMVC怎么在请求处理的过程中完成对异常的统一处理的呢?我们从源码来深度解读。
回到DispatcherServlet的doDispatcher方法
try { processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. mappedHandler = getHandler(processedRequest); if (mappedHandler == null || mappedHandler.getHandler() == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); if (!mappedHandler.applyPreHandle(processedRequest, response)) { return; } // Actually invoke the handler. mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } applyDefaultViewName(processedRequest, mv); mappedHandler.applyPostHandle(processedRequest, response, mv); } catch (Exception ex) { dispatchException = ex; } catch (Throwable err) { dispatchException = new NestedServletException("Handler dispatch failed", err); } processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
可以看到对请求处理的核心处理使用一个大的try/catch,如果出现异常,统一封装成dispatchException交给processDispatchResult方法进行处理。我们知道processDispatchResult方法用来对返回视图进行操作,而同时也对异常进行统一处理。
在processDispatchResult中,首先对异常进行判断。
if (exception != null) { if (exception instanceof ModelAndViewDefiningException) { logger.debug("ModelAndViewDefiningException encountered", exception); mv = ((ModelAndViewDefiningException) exception).getModelAndView(); } else { Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null); mv = processHandlerException(request, response, handler, exception); errorView = (mv != null); } }
如果不是特殊的ModelAndViewDefiningException,则由processHandlerException来操作。
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // Check registered HandlerExceptionResolvers... ModelAndView exMv = null; // 遍历所有注册的异常处理器,由异常处理器进行处理 for (HandlerExceptionResolver handlerExceptionResolver : this.handlerExceptionResolvers) { exMv = handlerExceptionResolver.resolveException(request, response, handler, ex); if (exMv != null) { break; } } // 如果异常视图存在,则转向异常视图 if (exMv != null) { if (exMv.isEmpty()) { request.setAttribute(EXCEPTION_ATTRIBUTE, ex); return null; } // We might still need view name translation for a plain error model... if (!exMv.hasView()) { exMv.setViewName(getDefaultViewName(request)); } if (logger.isDebugEnabled()) { logger.debug("Handler execution resulted in exception - forwarding to resolved error view: " + exMv, ex); } WebUtils.exposeErrorRequestAttributes(request, ex, getServletName()); return exMv; } throw ex; }
我们主要关注异常处理器对异常的处理,SpringMVC通过HandlerExceptionResolver的resolveException调用实现类的实际实现方法doResolveException。
推荐博客
ExceptionHandlerExceptionResolver
ExceptionHandlerExceptionResolver支持了@ExceptionHandler注解的实现。它的抽象基类AbstractHandlerMethodExceptionResolver继承了AbstractHandlerExceptionResolver,doResolveException方法实际调用ExceptionHandlerExceptionResolver的doResolveHandlerMethodException方法。
protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { return this.doResolveHandlerMethodException(request, response, (HandlerMethod)handler, ex); } protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) { // 根据HandlerMethod和exception获取异常处理的Method ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } // 设置异常处理方法的参数解析器和返回值解析器 exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // 执行异常处理方法 try { if (logger.isDebugEnabled()) { logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod); } Throwable cause = exception.getCause(); if (cause != null) { // Expose cause as provided argument as well exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod); } else { // Otherwise, just the given exception as-is exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod); } } catch (Throwable invocationEx) { // Any other than the original exception is unintended here, // probably an accident (e.g. failed assertion or the like). if (invocationEx != exception && logger.isWarnEnabled()) { logger.warn("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx); } // Continue with default processing of the original exception... return null; } // 对返回的视图模型进行处理 if (mavContainer.isRequestHandled()) { return new ModelAndView(); } else { ModelMap model = mavContainer.getModel(); HttpStatus status = mavContainer.getStatus(); ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status); mav.setViewName(mavContainer.getViewName()); if (!mavContainer.isViewReference()) { mav.setView((View) mavContainer.getView()); } if (model instanceof RedirectAttributes) { Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); request = webRequest.getNativeRequest(HttpServletRequest.class); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } }
我们主要关注的是如何匹配到异常处理方法的,也就是 ExceptionHandlerExceptionResolver中的 getExceptionHandlerMethod
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); // 从当前Controller中匹配异常处理Method,此处我们暂时不分析 if (handlerMethod != null) { ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType); if (resolver == null) { resolver = new ExceptionHandlerMethodResolver(handlerType); this.exceptionHandlerCache.put(handlerType, resolver); } Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method); } } // 从ControllerAdvice中匹配异常处理Method // 我们知道容器初始化的时候,已经寻找所有标注了@ControllerAdvice的类,并将此类标注了@ExceptionHandler的方法放到当前类的exceptionHandlerAdviceCache中 //遍历所有的@ControllerAdvice生成的缓存 for (Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { if (entry.getKey().isApplicableToBeanType(handlerType)) { //拿到ExceptionHandlerMethodResolver,这个对象里包含了标注为@ExceptionHandler的Key为Excpthion,value为Method的缓存Map ExceptionHandlerMethodResolver resolver = entry.getValue(); //寻找ExceptionHandlerMethodResolver有没有标注@ExceptionHandler能匹配当前异常的方法 Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); } } } return null; } public Method resolveMethod(Exception exception) { Method method = this.resolveMethodByExceptionType(exception.getClass()); if (method == null) { Throwable cause = exception.getCause(); if (cause != null) { method = this.resolveMethodByExceptionType(cause.getClass()); } } return method; } public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) { //先从exceptionLookupCache缓存中拿,第一次肯定拿不到,因为我们是存在mappedMethods这个缓存中 Method method = (Method)this.exceptionLookupCache.get(exceptionType); if (method == null) { method = this.getMappedMethod(exceptionType); this.exceptionLookupCache.put(exceptionType, method != null ? method : NO_METHOD_FOUND); } return method != NO_METHOD_FOUND ? method : null; } private Method getMappedMethod(Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList(); //拿到所有的Key Iterator var3 = this.mappedMethods.keySet().iterator(); while(var3.hasNext()) { Class<? extends Throwable> mappedException = (Class)var3.next(); //判断exceptionType是不是mappedException本身或者其子类 if (mappedException.isAssignableFrom(exceptionType)) { matches.add(mappedException); } } if (!matches.isEmpty()) { Collections.sort(matches, new ExceptionDepthComparator(exceptionType)); //返回匹配到异常的Method return (Method)this.mappedMethods.get(matches.get(0)); } else { return null; } }
匹配到exceptionHandlerMethod后,设置一些方法执行的环境,然后调用ServletInvocableHandlerMethod中的invokeAndHandle去执行,这个调用过程和正常请求的调用就是一致了。
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, new Object[]{exception, cause, handlerMethod});
这里就是调用异常处理的方法,总体来说,就是SpringMvc启动的时候初始化异常处理的组件,将 @ControllerAdvice标记的特殊类和@ExceptionHandler 标记的方法存入缓存中,当目标Controller出现异常的时候,就通过抛出的异常在缓存中找到对应的处理方法,然后去调用对应的异常处理方法就OK了。