小架构step系列31:处理异常
1 概述
在Web请求处理的过程中,如果发生异常,就需要对异常进行处理。异常可能是各种各样的,有些需要特殊处理,有些通用处理即可。这些处理的逻辑如果到处放,维护起来也比较困难,Spring提供的异常处理机制考虑了这些问题,本文了解一下其原理。
2 原理
2.1 配置HandlerExceptionResolver
开启@EnableWebMvc的时候,会初始化HandlerExceptionResolver:
- 初始化的时候,先通过WebMvcConfigurer的configureHandlerExceptionResolvers配置自定义的HandlerExceptionResolver;
- 如果没有配置则提供默认的三个HandlerExceptionResolver:ExceptionHandlerExceptionResolver、ResponseStatusExceptionResolver、DefaultHandlerExceptionResolver。默认配置的和通过WebMvcConfigurer的configureHandlerExceptionResolvers配置的属于二选一,而不是合并起来。
- 再通过WebMvcConfigurer的extendHandlerExceptionResolvers增加额外的HandlerExceptionResolver,这个是额外增加的,不影响之前添加的。
- 把所有HandlerExceptionResolver设置到HandlerExceptionResolverComposite,它也实现了HandlerExceptionResolver接口,并成为Spring的一个bean,这样可以通过接口查找到这个bean,方便注入。
// 源码位置:org.springframework.web.servlet.config.annotation.EnableWebMvc
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}// 源码位置:org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}// 源码位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
@Bean
public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();// 1. 配置ExceptionResolverconfigureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;
}// 源码位置:org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration
@Override
protected void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {// 2. configurers为WebMvcConfigurerComposite,里面注入了实现了WebMvcConfigurer接口的所有类this.configurers.configureHandlerExceptionResolvers(exceptionResolvers);
}// 源码位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurerComposite
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {for (WebMvcConfigurer delegate : this.delegates) {// 3. 遍历所有的WebMvcConfigurer,执行它的configureHandlerExceptionResolvers,把得到的HandlerExceptionResolver加到exceptionResolvers列表里delegate.configureHandlerExceptionResolvers(exceptionResolvers);}
}// 回到WebMvcConfigurationSupport的handlerExceptionResolver继续处理
// 源码位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
@Bean
public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();// 1. 配置ExceptionResolverconfigureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {// 4. 如果WebMvcConfigurer没有提供HandlerExceptionResolver,则添加默认的HandlerExceptionResolveraddDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}extendHandlerExceptionResolvers(exceptionResolvers);HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers);return composite;
}// 源码位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers, ContentNegotiationManager mvcContentNegotiationManager) {// 5. 添加ExceptionHandlerExceptionResolver,并把ArgumentResolver、ReturnValueHandler、MessageConverter等组装进去,相当于要处理一个请求的形式ExceptionHandlerExceptionResolver exceptionHandlerResolver = createExceptionHandlerExceptionResolver();exceptionHandlerResolver.setContentNegotiationManager(mvcContentNegotiationManager);exceptionHandlerResolver.setMessageConverters(getMessageConverters());exceptionHandlerResolver.setCustomArgumentResolvers(getArgumentResolvers());exceptionHandlerResolver.setCustomReturnValueHandlers(getReturnValueHandlers());if (jackson2Present) {exceptionHandlerResolver.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));}if (this.applicationContext != null) {exceptionHandlerResolver.setApplicationContext(this.applicationContext);}exceptionHandlerResolver.afterPropertiesSet();exceptionResolvers.add(exceptionHandlerResolver);// 6. 添加ResponseStatusExceptionResolverResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver();responseStatusResolver.setMessageSource(this.applicationContext);exceptionResolvers.add(responseStatusResolver);// 7. 添加DefaultHandlerExceptionResolverexceptionResolvers.add(new DefaultHandlerExceptionResolver());
}// 回到WebMvcConfigurationSupport的handlerExceptionResolver继续处理
// 源码位置:org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport
@Bean
public HandlerExceptionResolver handlerExceptionResolver(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();// 1. 配置ExceptionResolverconfigureHandlerExceptionResolvers(exceptionResolvers);if (exceptionResolvers.isEmpty()) {// 4. 如果WebMvcConfigurer没有提供HandlerExceptionResolver,则添加默认的HandlerExceptionResolveraddDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);}// 8. WebMvcConfigurer还可以通过extendHandlerExceptionResolvers配置exceptionResolverextendHandlerExceptionResolvers(exceptionResolvers);// 9. HandlerExceptionResolverComposite也实现HandlerExceptionResolver接口,它成为一个可以查找的bean,里面带了上面配置的所有exceptionResolverHandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();composite.setOrder(0);composite.setExceptionResolvers(exceptionResolvers); // 设置exceptionResolverreturn composite;
}
2.2 处理异常流程
客户端发起的http请求由DispatcherServlet处理,DispatcherServlet初始化的时候把上面初始化的ExceptionResolver对应的bean都取到,然后在处理请求的时候,通过ExceptionResolver进行异常处理。
// DispatcherServlet初始化的时候把HandlerExceptionResolver对应的bean都找到放handlerExceptionResolvers里
// 源码位置:org.springframework.web.servlet.DispatcherServlet
private void initHandlerExceptionResolvers(ApplicationContext context) {this.handlerExceptionResolvers = null;if (this.detectAllHandlerExceptionResolvers) {// 1. 根据HandlerExceptionResolver接口获取实现了该接口的bean,如果没有自定义额外的bean,则默认能找到两个:// org.springframework.boot.web.servlet.error.DefaultErrorAttributes// org.springframework.web.servlet.handler.HandlerExceptionResolverComposite// 注意:通过WebMvcConfigurer配置的并不是bean,它配置的HandlerExceptionResolver是存到HandlerExceptionResolverComposite里的Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);if (!matchingBeans.isEmpty()) {// 2. 赋值给handlerExceptionResolvers,供后面处理异常的时候使用this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);}}else {try {// 3. detectAllHandlerExceptionResolvers默认为true,如果关掉了,需要提供一个名称为handlerExceptionResolver的HandlerExceptionResolver// HANDLER_EXCEPTION_RESOLVER_BEAN_NAME = "handlerExceptionResolver"HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);this.handlerExceptionResolvers = Collections.singletonList(her);}catch (NoSuchBeanDefinitionException ex) {}}if (this.handlerExceptionResolvers == null) {this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);if (logger.isTraceEnabled()) {logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +"': using default strategies from DispatcherServlet.properties");}}
}// DispatcherServlet在处理http请求的时候,从handlerExceptionResolvers里取出HandlerExceptionResolver进行异常处理
// 源码位置:org.springframework.web.servlet.DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {HttpServletRequest processedRequest = request;HandlerExecutionChain mappedHandler = null;boolean multipartRequestParsed = false;WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);try {ModelAndView mv = null;Exception dispatchException = null;try {processedRequest = checkMultipart(request);multipartRequestParsed = (processedRequest != request);mappedHandler = getHandler(processedRequest);if (mappedHandler == null) {noHandlerFound(processedRequest, response);return;}HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());String method = request.getMethod();boolean isGet = HttpMethod.GET.matches(method);if (isGet || HttpMethod.HEAD.matches(method)) {long lastModified = ha.getLastModified(request, mappedHandler.getHandler());if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {return;}}if (!mappedHandler.applyPreHandle(processedRequest, response)) {return;}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);}// 4. 处理请求的结果processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);}// 省略部分代码
}// 源码位置:org.springframework.web.servlet.DispatcherServlet
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,@Nullable Exception exception) throws Exception {boolean errorView = false;if (exception != null) {if (exception instanceof ModelAndViewDefiningException) {logger.debug("ModelAndViewDefiningException encountered", exception);mv = ((ModelAndViewDefiningException) exception).getModelAndView();}else {Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);// 5. 如果有异常,则把异常处理成一个结果mv = processHandlerException(request, response, handler, exception);errorView = (mv != null);}}if (mv != null && !mv.wasCleared()) {render(mv, request, response);if (errorView) {WebUtils.clearErrorRequestAttributes(request);}}else {if (logger.isTraceEnabled()) {logger.trace("No view rendering, null ModelAndView returned.");}}if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {return;}if (mappedHandler != null) {mappedHandler.triggerAfterCompletion(request, response, null);}
}// 源码位置:org.springframework.web.servlet.DispatcherServlet
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception {request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);ModelAndView exMv = null;// 6. 遍历所有ExceptionResolver,执行通用方法resolveException,只要执行到一个有返回值的,则忽略后面的ExceptionResolver// handlerExceptionResolvers是上面DispatcherServlet初始化的时候赋值的,默认有两个:// org.springframework.boot.web.servlet.error.DefaultErrorAttributes// org.springframework.web.servlet.handler.HandlerExceptionResolverComposite// 重点看一下HandlerExceptionResolverComposite的处理,它里面设置了所有配置的非bean的ExceptionResolverif (this.handlerExceptionResolvers != null) {for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {exMv = resolver.resolveException(request, response, handler, ex);if (exMv != null) {break;}}}if (exMv != null) {if (exMv.isEmpty()) {request.setAttribute(EXCEPTION_ATTRIBUTE, ex);return null;}if (!exMv.hasView()) {String defaultViewName = getDefaultViewName(request);if (defaultViewName != null) {exMv.setViewName(defaultViewName);}}if (logger.isTraceEnabled()) {logger.trace("Using resolved error view: " + exMv, ex);}else if (logger.isDebugEnabled()) {logger.debug("Using resolved error view: " + exMv);}WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());return exMv;}throw ex;
}// 源码位置:org.springframework.web.servlet.handler.HandlerExceptionResolverComposite
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {// 7. 遍历包装的HandlerExceptionResolver,执行resolveException()方法,默认包装了3个,主要看一下ExceptionHandlerExceptionResolver:// org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver// org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver// org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolverif (this.resolvers != null) {for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);if (mav != null) {return mav;}}}return null;
}// 继承关系:ExceptionHandlerExceptionResolver < AbstractHandlerMethodExceptionResolver < AbstractHandlerExceptionResolver
// resolveException()由AbstractHandlerExceptionResolver提供,ExceptionHandlerExceptionResolver重载了doResolveException()方法
// org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) {if (shouldApplyTo(request, handler)) {prepareResponse(ex, response);// 8. 处理异常ModelAndView result = doResolveException(request, response, handler, ex);if (result != null) {if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) {logger.debug(buildLogMessage(ex, request) + (result.isEmpty() ? "" : " to " + result));}logException(ex, request);}return result;}else {return null;}
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {// 9. 获取处理异常的方法,handlerMethod对应的是Controller接收请求的MethodServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}// 省略部分代码
}// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;if (handlerMethod != null) {// 10. 取到Controller的类handlerType = handlerMethod.getBeanType();// 11. 根据Controller类从缓存exceptionHandlerCache里取ExceptionHandlerMethodResolverExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {// 12. 如果exceptionHandlerCache没有取到ExceptionHandlerMethodResolver,则创建一个resolver = new ExceptionHandlerMethodResolver(handlerType);this.exceptionHandlerCache.put(handlerType, resolver);}Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext);}if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();if (advice.isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);}}}return null;
}// 源码位置:org.springframework.web.method.annotation.ExceptionHandlerMethodResolver
public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
public ExceptionHandlerMethodResolver(Class<?> handlerType) {// 13. EXCEPTION_HANDLER_METHODS为一个function,主要是过滤是否有@ExceptionHandler注解,// 即遍历Controller里的方法,把标注了@ExceptionHandler注解的方法过滤出来for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {addExceptionMapping(exceptionType, method); // 过滤出的结果放到this.mappedMethods}}
}// 回到ExceptionHandlerExceptionResolver的getExceptionHandlerMethod()继续处理
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(@Nullable HandlerMethod handlerMethod, Exception exception) {Class<?> handlerType = null;if (handlerMethod != null) {// 10. 取到Controller的类handlerType = handlerMethod.getBeanType();// 11. 根据Controller类从缓存exceptionHandlerCache里取ExceptionHandlerMethodResolverExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);if (resolver == null) {// 12. 如果exceptionHandlerCache没有取到ExceptionHandlerMethodResolver,则创建一个resolver = new ExceptionHandlerMethodResolver(handlerType);// 14. 把新创建的ExceptionHandlerMethodResolver的缓存起来,避免每次请求都需要重复创建this.exceptionHandlerCache.put(handlerType, resolver);}// 15. @ExceptionHandler注解需要指定一种Exception类型,需要根据具体的异常匹配到具体的methodMethod method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(handlerMethod.getBean(), method, this.applicationContext);}if (Proxy.isProxyClass(handlerType)) {handlerType = AopUtils.getTargetClass(handlerMethod.getBean());}}// 16. ExceptionHandlerExceptionResolver初始化的时候initExceptionHandlerAdviceCache(),会把@ControllerAdvice标注的类里的方法也作为处理异常的方法// 通过具体的异常匹配到处理异常的方法,原理和上面一样for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {ControllerAdviceBean advice = entry.getKey();if (advice.isApplicableToBeanType(handlerType)) {ExceptionHandlerMethodResolver resolver = entry.getValue();Method method = resolver.resolveMethod(exception);if (method != null) {return new ServletInvocableHandlerMethod(advice.resolveBean(), method, this.applicationContext);}}}return null;
}
private void initExceptionHandlerAdviceCache() {// 省略部分代码// 17. 如果类上标注了@ControllerAdvice,这个类也会被Spring实例化为bean,也需要用ExceptionHandlerMethodResolver从这些类里找到对应处理异常的方法List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());for (ControllerAdviceBean adviceBean : adviceBeans) {Class<?> beanType = adviceBean.getBeanType();if (beanType == null) {throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);}ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);if (resolver.hasExceptionMappings()) {this.exceptionHandlerAdviceCache.put(adviceBean, resolver);}if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) {this.responseBodyAdvice.add(adviceBean);}}// 省略部分代码
}// 回到ExceptionHandlerExceptionResolver的doResolveHandlerMethodException()继续处理
// 源码位置:org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {// 9. 获取处理异常的方法,handlerMethod对应的是Controller接收请求的MethodServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);if (exceptionHandlerMethod == null) {return null;}// 10. 给ServletInvocableHandlerMethod设置argumentResolvers、returnValueHandlers等,并调用实际方法处理异常// 可以看作:当调用Controller的一个业务方法service1()产生异常的时候,相当于执行的是对应的异常处理方法exception1(),// 两个方法的过程基本一样:处理参数、调用方法执行逻辑、处理返回值等。if (this.argumentResolvers != null) {exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);}if (this.returnValueHandlers != null) {exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);}ServletWebRequest webRequest = new ServletWebRequest(request, response);ModelAndViewContainer mavContainer = new ModelAndViewContainer();ArrayList<Throwable> exceptions = new ArrayList<>();try {if (logger.isDebugEnabled()) {logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);}Throwable exToExpose = exception;while (exToExpose != null) {exceptions.add(exToExpose);Throwable cause = exToExpose.getCause();exToExpose = (cause != exToExpose ? cause : null);}Object[] arguments = new Object[exceptions.size() + 1];exceptions.toArray(arguments);arguments[arguments.length - 1] = handlerMethod;exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments);}catch (Throwable invocationEx) {if (!exceptions.contains(invocationEx) && logger.isWarnEnabled()) {logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);}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();RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);}return mav;}
}
2.3 使用
SpringMVC在处理Http请求的时候,留了两种方式供介入异常处理:
1) 自行定义HandlerExceptionResolver,需实现HandlerExceptionResolver接口
public interface HandlerExceptionResolver {ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
然后用WebMvcConfigurer的下面接口把它注入到Spring中:
public interface WebMvcConfigurer {default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {}
}
注意:
- 如果configureHandlerExceptionResolvers()提供了HandlerExceptionResolver,那么默认的HandlerExceptionResolver就没有了,需要自行处理所有的异常。如果只是针对某几种场景的异常希望自行处理,需要通过extendHandlerExceptionResolvers()提供。
- 处理请求的时候,只会用一个HandlerExceptionResolver,只要有一个HandlerExceptionResolver返回了处理结果(不为null),那么后面的HandlerExceptionResolver就被忽略掉了。
- 这种方式提供的HandlerExceptionResolver需要处理比较多事情,如何通过异常匹配到具体的异常处理方法、如何处理返回值等,而这一块Spring已经提供比较好的实现,所以非特殊情况不推荐自行实现HandlerExceptionResolver。
2) 使用ExceptionHandlerExceptionResolver提供的@ControllerAdvice和@ExceptionHandler机制
这种情形也有两种方式:
- 第一种是直接在Controller里的方法加上@ExceptionHandler注解,这样这个方法就会成为处理异常的方法了。参数是要处理的异常,返回值和普通的Controller方法一样规则。如果只希望这个Controller的请求产生的异常有一种特殊的处理方式,那么推荐用这个方法。
- 第二种是在某个类上先加上@ControllerAdvice注解,这样Spring会把这个类作为Bean加载。这个类里的方法如果加上@ExceptionHandler注解,那么也会成为处理异常的方法。如果是处理全局异常,推荐用这个方法。
3 架构一小步
1) 特殊处理某个Controller的异常时,把处理异常处理方法放Controller里,并加上@ExceptionHandler注解。
2) 处理全局异常时,单独用一个类提供方法处理,类上面标注@ControllerAdvice注解,方法标注@ExceptionHandler注解。