交流
商城
MCN
登入
注册
首页
提问
分享
讨论
建议
公告
动态
发表新帖
发表新帖
springmvc第5 章:HandlerExceptionResolver
分享
未结
0
963
李延
LV6
2021-05-26
悬赏:20积分
# 作用 mvc的全局异常处理 # 初始化与调用 ## 初始化 在DispatcherServlet的初始化方法中,我们看到调用了initHandlerExceptionResolvers方法。我们看一下: ```java private void initHandlerExceptionResolvers(ApplicationContext context) { this.handlerExceptionResolvers = null; if (this.detectAllHandlerExceptionResolvers) { // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts. Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values()); // We keep HandlerExceptionResolvers in sorted order. AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers); } } else { try { HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class); this.handlerExceptionResolvers = Collections.singletonList(her); } catch (NoSuchBeanDefinitionException ex) { // Ignore, no HandlerExceptionResolver is fine too. } } // Ensure we have at least some HandlerExceptionResolvers, by registering // default HandlerExceptionResolvers if no other resolvers are found. 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"); } } } ``` 整改过程就是从BeanFactroy中加载HandlerExceptionResolver对象 ## 调用 在DispatcherServlet中,首先通过HandlerAdapter处理完了请求,在处理过程中如果有异常发生,则会循环调用每一个HandlerExceptionResolver,查看是否有对应的异常处理。 # 主要实现 默认有2个注入: 1. DefaultErrorAttributes 2. HandlerExceptionResolverComposite # DefaultErrorAttributes ```java @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { storeErrorAttributes(request, ex); return null; } private void storeErrorAttributes(HttpServletRequest request, Exception ex) { request.setAttribute(ERROR_ATTRIBUTE, ex); } ``` 这个里面没有处理逻辑,只是将异常信息保存在了request中。 # HandlerExceptionResolverComposite 从名称就可以看出来,他是一个集合,自己本身没有任何处理逻辑,都是交给它的成员变量来处理。 ```java @Override @Nullable public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (this.resolvers != null) { for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) { ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex); if (mav != null) { return mav; } } } return null; } ``` 那么我们看具体看一下resolvers对象数据是在哪加载的,都加载了什么 ## 初始化 HandlerExceptionResolverComposite对象是在WebMvcConfigurationSupport中通过@Bean加载进来的。 ```java @Bean public HandlerExceptionResolver handlerExceptionResolver( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>(); configureHandlerExceptionResolvers(exceptionResolvers); if (exceptionResolvers.isEmpty()) { addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager); } extendHandlerExceptionResolvers(exceptionResolvers); HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite(); composite.setOrder(0); composite.setExceptionResolvers(exceptionResolvers); return composite; } ``` 在这里我们就可以看到为它设置ExceptionResolvers过程。我们看一下addDefaultHandlerExceptionResolvers方法,看看都加载了哪些默认的类。 ```java protected final void addDefaultHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers, ContentNegotiationManager mvcContentNegotiationManager) { 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); ResponseStatusExceptionResolver responseStatusResolver = new ResponseStatusExceptionResolver(); responseStatusResolver.setMessageSource(this.applicationContext); exceptionResolvers.add(responseStatusResolver); exceptionResolvers.add(new DefaultHandlerExceptionResolver()); } ``` 这里我们看到有3个对象被加载进来 - ExceptionHandlerExceptionResolver - ResponseStatusExceptionResolver - DefaultHandlerExceptionResolver 这3个对象都继承自AbstractHandlerExceptionResolver。其中ExceptionHandlerExceptionResolver还继承自ExceptionHandlerExceptionResolver。所以我们先解析这两个抽象接口,再逐步解析这3个类。 # AbstractHandlerExceptionResolver 我们直接看resolveException方法: ```java @Override @Nullable public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { //判断是否支持当前异常处理,如果支持进行处理 if (shouldApplyTo(request, handler)) { //准备Response prepareResponse(ex, response); //处理异常 ModelAndView result = doResolveException(request, response, handler, ex); if (result != null) { // Print debug message when warn logger is not enabled. if (logger.isDebugEnabled() && (this.warnLogger == null || !this.warnLogger.isWarnEnabled())) { logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); } // Explicitly configured warn logger in logException method. logException(ex, request); } return result; } else { return null; } } ``` 其中doResolveException是模版方法,子类实现 # ExceptionHandlerExceptionResolver 主要看doResolveException和shouldApplyTo ```java @Override protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) { if (handler == null) { return super.shouldApplyTo(request, null); } else if (handler instanceof HandlerMethod) { HandlerMethod handlerMethod = (HandlerMethod) handler; handler = handlerMethod.getBean(); return super.shouldApplyTo(request, handler); } else if (hasGlobalExceptionHandlers() && hasHandlerMappings()) { return super.shouldApplyTo(request, handler); } else { return false; } } ``` 我们看到,对于handler是HandlerMethod的都是交给这个类来处理的 ## doResolveException ```java @Override @Nullable protected final ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { HandlerMethod handlerMethod = (handler instanceof HandlerMethod ? (HandlerMethod) handler : null); return doResolveHandlerMethodException(request, response, handlerMethod, ex); } ``` 这里只是转换了一下参数类型,还是留给子类处理 # ExceptionHandlerExceptionResolver 这个类我们关注2个方法一个是初始化方法,一个是doResolveHandlerMethodException ## 初始化 ```java @Override public void afterPropertiesSet() { // Do this first, it may add ResponseBodyAdvice beans 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); } } ``` 这里我们又看到了熟悉的代码与HandlerAdapter相同也加载了argumentResolvers和returnValueHandlers,所以不做解析。我们直接看initExceptionHandlerAdviceCache方法 ### initExceptionHandlerAdviceCache ```java private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } //获取到有ControllerAdvice注解的类 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对象 ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType); //判断是否有异常处理的mapping if (resolver.hasExceptionMappings()) { this.exceptionHandlerAdviceCache.put(adviceBean, resolver); } if (ResponseBodyAdvice.class.isAssignableFrom(beanType)) { this.responseBodyAdvice.add(adviceBean); } } if (logger.isDebugEnabled()) { int handlerSize = this.exceptionHandlerAdviceCache.size(); int adviceSize = this.responseBodyAdvice.size(); if (handlerSize == 0 && adviceSize == 0) { logger.debug("ControllerAdvice beans: none"); } else { logger.debug("ControllerAdvice beans: " + handlerSize + " @ExceptionHandler, " + adviceSize + " ResponseBodyAdvice"); } } } ``` 在第一步中我们看到获取了ControllerAdvice注解的类。这个在HandlerAdapter中也有说明。之后创建了ExceptionHandlerMethodResolver对象。我们看一下它的构造方法 ```java public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class); public ExceptionHandlerMethodResolver(Class<?> handlerType) { for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) { addExceptionMapping(exceptionType, method); } } } ``` 基本和之前一样的方式,找到有ExceptionHandler注解的方法。我们看一下addExceptionMapping ```java private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) { Method oldMethod = this.mappedMethods.put(exceptionType, method); if (oldMethod != null && !oldMethod.equals(method)) { throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" + exceptionType + "]: {" + oldMethod + ", " + method + "}"); } } ``` 这里我们看到他是将其保存在了一个map中,key是异常类型,value就是处理异常的method 我们再会到HandlerExceptionResolver类。下面就是通过hasExceptionMappings方法判断,使用有异常处理的方法。有就记录下来。 ## doResolveHandlerMethodException ```java @Override @Nullable protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { ServletInvocableHandlerMethod 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); } ServletWebRequest webRequest = new ServletWebRequest(request, response); ModelAndViewContainer mavContainer = new ModelAndViewContainer(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + 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 (or its cause) is unintended here, // probably an accident (e.g. failed assertion or the like). if (invocationEx != exception && invocationEx != exception.getCause() && logger.isWarnEnabled()) { logger.warn("Failure in @ExceptionHandler " + 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(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } ``` 这里我们发现处理逻辑和HandlerAdapter十分相似,也是交给ServletInvocableHandlerMethod来处理。过程就不再重复
回帖
消灭零回复
提交回复
热议榜
java 相关知识分享
8
好的程序员与不好的程序员
6
写给工程师的十条精进原则
5
spring boot以jar包运行配置的logback日志文件没生成
5
一步一步分析SpringBoot启动源码(一)
5
MockMvc测试
5
【吐槽向】是不是有个吐槽的板块比较好玩
4
logstash jdbc同步mysql多表数据到elasticsearch
3
IntelliJ IDEA 优质License Server
3
.gitignore忽略规则
3
SpringBoot启动源码分析
3
一步一步分析SpringBoot启动源码(三)
3
2
一步一步分析SpringBoot启动源码(二)
2
积分不够将无法发表新帖
2
官方产品
Meta-Boot - 基于MCN
MCN - 快速构建SpringBoot应用
微信扫码关注公众号