当前位置: 首页 >服务端 > Spring MVC源码(四) ----- 统一异常处理原理解析

Spring MVC源码(四) ----- 统一异常处理原理解析

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的简单使用

@ControllerAdvicepublic 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, "数组越界异常");retu 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, "输入参数错误");retu 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, "未知异常");retu responseDTO;}}

我们看看 @ControllerAdvice

@Target({ElementType.TYPE})@Retention(RetentionPolicy.RUNTIME)@Documented@Componentpublic @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.xsdhttp://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsdhttp://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsdhttp://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.xsdhttp://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"><context:component-scanbase-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容器里注入具体的对象。**/@Overridepublic 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<?> retuValueHandlers = getRetuValueHandlers(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 deprecatedBoolean ignoreDefaultModel = Boolean.valueOf(element.getAttribute("ignoreDefaultModelOnRedirect"));handlerAdapterDef.getPropertyValues().add("ignoreDefaultModelOnRedirect", ignoreDefaultModel);}if (argumentResolvers != null) {handlerAdapterDef.getPropertyValues().add("customArgumentResolvers", argumentResolvers);}if (retuValueHandlers != null) {handlerAdapterDef.getPropertyValues().add("customRetuValueHandlers", retuValueHandlers);}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 (retuValueHandlers != null) {exceptionHandlerExceptionResolver.getPropertyValues().add("customRetuValueHandlers", retuValueHandlers);}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 "tued off"MvcNamespaceUtils.registerDefaultComponents(parserContext, source);parserContext.popAndRegisterContainingComponent();retu null;}}
通过上面代码的分析, 我们可以找到ExceptionHandlerExceptionResolver这个类来用于处理Spring MVC的各种异常,那ExceptionHandlerExceptionResolver具体又是如何跟ControllerAdvice配合使用来处理各种异常的呢?我们来看看ExceptionHandlerExceptionResolver里的关键代码:
package org.springframework.web.servlet.mvc.method.annotation;//我们考到这个类实现了InitializingBean,则容器初始化的时候在实例化此Bean后会调用afterPropertiesSet()public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolverimplements ApplicationContextAware, InitializingBean {//这里有个map用于保存ControllerAdviceBean和ExceptionHandlerMethodResolverprivate final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache =new LinkedHashMap<ControllerAdviceBean, ExceptionHandlerMethodResolver>();//这个方法是由spring 容器调用的@Overridepublic void afterPropertiesSet() {// Do this first, it may add ResponseBodyAdvice beans//这个方法里会处理ExceptionHandlerinitExceptionHandlerAdviceCache();if (this.argumentResolvers == null) {List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);}if (this.retuValueHandlers == null) {List<HandlerMethodRetuValueHandler> handlers = getDefaultRetuValueHandlers();this.retuValueHandlers = new HandlerMethodRetuValueHandlerComposite().addHandlers(handlers);}}/***这个方法里会在spring ioc容器里找出标注了@ControllerAdvice的类,如果有方法标注了@ExceptionHandler会生成一个ExceptionHandlerMethodResolver类用于处理异常并放到exceptionHandlerAdviceCache这个map缓存类里。**/private void initExceptionHandlerAdviceCache() {if (getApplicationContext() == null) {retu;}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的MethodExceptionHandlerMethodResolver 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) {retu 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) {retu new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);}}}retu null;}}

我们来看看 ExceptionHandlerMethodResolver这个类

public class ExceptionHandlerMethodResolver {public static final MethodFilter EXCEPTION_HANDLER_METHODS = new MethodFilter() {public boolean matches(Method method) {retu 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,值为当前Methodprivate 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 + "}");retu result;}protected void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {//判断此方法是否标记@ExceptionHandler,如果没有则返回null,如果有标记则返回ExceptionHandlerExceptionHandler 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);retu;}// Determine handler adapter for the current request.HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());if (!mappedHandler.applyPreHandle(processedRequest, response)) {retu;}// Actually invoke the handler.mv = ha.handle(processedRequest, response, mappedHandler.getHandler());if (asyncManager.isConcurrentHandlingStarted()) {retu;}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);retu 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());retu 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) {retu this.doResolveHandlerMethodException(request, response, (HandlerMethod)handler, ex);}protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {// 根据HandlerMethod和exception获取异常处理的MethodServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {retu null;}// 设置异常处理方法的参数解析器和返回值解析器exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);exceptionHandlerMethod.setHandlerMethodRetuValueHandlers(this.retuValueHandlers);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 wellexceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);}else {// Otherwise, just the given exception as-isexceptionHandlerMethod.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.isWaEnabled()) {logger.wa("Failed to invoke @ExceptionHandler method: " + exceptionHandlerMethod, invocationEx);}// Continue with default processing of the original exception...retu null;}// 对返回的视图模型进行处理if (mavContainer.isRequestHandled()) {retu 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);}retu 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) {retu 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的缓存MapExceptionHandlerMethodResolver resolver = entry.getValue();//寻找ExceptionHandlerMethodResolver有没有标注@ExceptionHandler能匹配当前异常的方法Method method = resolver.resolveMethod(exception);if (method != null) {retu new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method);}}}retu 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());}}retu 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);}retu method != NO_METHOD_FOUND ? method : null;}private Method getMappedMethod(Class<? extends Throwable> exceptionType) {List<Class<? extends Throwable>> matches = new ArrayList();//拿到所有的KeyIterator 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));//返回匹配到异常的Methodretu (Method)this.mappedMethods.get(matches.get(0));} else {retu null;}}

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

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

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

 

作者:chen_hao
来源链接:https://www.cnblogs.com/java-chen-hao/p/11190659.html

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。





本文链接:https://www.javaclub.cn/server/112921.html

标签:异常处理
分享给朋友:

“Spring MVC源码(四) ----- 统一异常处理原理解析” 的相关文章