# 第11章-SpringMVC异常处理源码和@EnableWebMvc原理 ## 视图解析器不重要了,不细述了 因为现在都是前后端分离的架构,不太需要视图解析器了,有兴趣的可以自己研究。 ## 异常处理流程 ### 测试类 #### HelloController ```java package cn.imlql.web.controller; @Controller public class HelloController { public HelloController(){ System.out.println("HelloController....."); } @GetMapping("/hello") public String sayHello(@RequestParam(name = "name",required = true) String name, Integer i) { int x = 10 / i; return "index.jsp"; } } ``` ### 什么参数都不传 什么参数都不传,肯定是会报异常的,因为@RequestParam那里我加了个required。 #### DispatcherServlet#doDispatch() 报了一个缺少参数的错误,下面看看怎么处理的 #### DispatcherServlet#processDispatchResult() ```java private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception { boolean errorView = false; //如果有异常处理异常,以下if内全是异常处理环节 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); } } //上面所有的异常解析器都没能处理这个异常,下面直接炸.... // 动态策略。 Did the handler return a view to render? 为啥?@ResponseBody(提前在解析返回值的时候,就已经把数据写出去了,所以这一步就没有了) if (mv != null && !mv.wasCleared()) { render(mv, request, response); //渲染ModeAndView,来解析模型和视图;最终决定响应效果 if (errorView) { WebUtils.clearErrorRequestAttributes(request); } } else { if (logger.isTraceEnabled()) { logger.trace("No view rendering, null ModelAndView returned."); } } if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) { // Concurrent handling started during a forward return; } if (mappedHandler != null) { // Exception (if any) is already handled.. mappedHandler.triggerAfterCompletion(request, response, null); } } ``` #### DispatcherServlet#processHandlerException()准备处理异常 ```java protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { // Success and error responses may use different content types request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); // Check registered HandlerExceptionResolvers... ModelAndView exMv = null; //所有异常解析器继续解析 if (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; } // We might still need view name translation for a plain error model... 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; //如果所有的异常解析器都不能解析就直接抛出这个异常 } ``` ##### 三个异常解析器概述 - ExceptionHandlerExceptionResolver:所有@ExceptionHandler注解方式的异常处理由他来做,启动扫描了容器中所有标了@ControllerAdvice的类以及这个类里面所有@Exceptionhandler标注的方法,并且缓存这个方法能处理什么异常 - ResponseStatusExceptionResolver:找异常类上有没有@ResponseStatus注解 - DefaultHandlerExceptionResolver:异常是否是spring内部指定的异常,如果是,直接响应错误页sendError以及错误代码, 并返回new的空的ModelAndView(注意这里返回的是空,不是null) #### AbstractHandlerExceptionResolver#resolveException()解析异常 ```java @Override @Nullable //父类抽象类规定的模板 public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { 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; } } ``` #### AbstractHandlerMethodExceptionResolver#doResolveException() ```java 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); } ``` 下面开始进入实现类,因为index=0的是ExceptionHandlerExceptionResolver,就会先进入这个异常解析器 #### ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()寻找@ExceptionHandler注解标注的方法 ```java protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { //为当前异常找一个处理方法??? @ExceptionHandler注解标注的方法 ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } //异常解析器里面 还是利用了以前的 argumentResolvers和 returnValueHandlers扩展了异常解析的功能 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 exceptions = new ArrayList<>(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } // Expose causes as provided arguments as well 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); // efficient arraycopy call in ArrayList arguments[arguments.length - 1] = handlerMethod; exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } catch (Throwable invocationEx) { // Any other than the original exception (or a cause) is unintended here, // probably an accident (e.g. failed assertion or the like). if (!exceptions.contains(invocationEx) && 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 flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } ``` #### ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()遍历所有的@ControllerAdvice,看哪个类的方法能处理这个异常 ```java protected ServletInvocableHandlerMethod getExceptionHandlerMethod( @Nullable HandlerMethod handlerMethod, Exception exception) { Class handlerType = null; if (handlerMethod != null) { // Local exception handler methods on the controller class itself. // To be invoked through the proxy, even in case of an interface-based proxy. handlerType = handlerMethod.getBeanType(); 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 advice applicability check below (involving base packages, assignable types // and annotation presence), use target class instead of interface-based proxy. if (Proxy.isProxyClass(handlerType)) { handlerType = AopUtils.getTargetClass(handlerMethod.getBean()); } } //遍历所有的@ControllerAdvice,看哪个类的方法能处理这个异常 for (Map.Entry 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); } } } return null; } ``` #### 返回到ExceptionHandlerExceptionResolver#doResolveHandlerMethodException() 因为咱们没有@ControllerAdvice标注的类,所以这里会返回空 接着返回 #### 返回到DispatcherServlet#processHandlerException() 准备循环第二个异常解析器 接下来还是父类AbstractHandlerMethodExceptionResolver那个模板方法,前面写了,这里直接跳过 #### ResponseStatusExceptionResolver#doResolveException()处理@ResponseStatus注解标注的相关异常 ```java protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { if (ex instanceof ResponseStatusException) { return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); } //拿到 ResponseStatus 注解 ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (status != null) { return resolveResponseStatus(status, request, response, handler, ex); } if (ex.getCause() instanceof Exception) { return doResolveException(request, response, handler, (Exception) ex.getCause()); } } catch (Exception resolveEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx); } } return null; } ``` 因为咱们也没有标注@ResponseStatus注解,所以也是空,直接来到第三个异常解析器 #### 返回到DispatcherServlet#processHandlerException() #### DefaultHandlerExceptionResolver#doResolveException()处理SpringMVC底层的异常 ```java @Override @Nullable protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { //处理SpringMVC底层的异常 if (ex instanceof HttpRequestMethodNotSupportedException) { return handleHttpRequestMethodNotSupported( (HttpRequestMethodNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotSupportedException) { return handleHttpMediaTypeNotSupported( (HttpMediaTypeNotSupportedException) ex, request, response, handler); } else if (ex instanceof HttpMediaTypeNotAcceptableException) { return handleHttpMediaTypeNotAcceptable( (HttpMediaTypeNotAcceptableException) ex, request, response, handler); } else if (ex instanceof MissingPathVariableException) { return handleMissingPathVariable( (MissingPathVariableException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestParameterException) { return handleMissingServletRequestParameter( (MissingServletRequestParameterException) ex, request, response, handler); } else if (ex instanceof ServletRequestBindingException) { return handleServletRequestBindingException( (ServletRequestBindingException) ex, request, response, handler); } else if (ex instanceof ConversionNotSupportedException) { return handleConversionNotSupported( (ConversionNotSupportedException) ex, request, response, handler); } else if (ex instanceof TypeMismatchException) { return handleTypeMismatch( (TypeMismatchException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotReadableException) { return handleHttpMessageNotReadable( (HttpMessageNotReadableException) ex, request, response, handler); } else if (ex instanceof HttpMessageNotWritableException) { return handleHttpMessageNotWritable( (HttpMessageNotWritableException) ex, request, response, handler); } else if (ex instanceof MethodArgumentNotValidException) { return handleMethodArgumentNotValidException( (MethodArgumentNotValidException) ex, request, response, handler); } else if (ex instanceof MissingServletRequestPartException) { return handleMissingServletRequestPartException( (MissingServletRequestPartException) ex, request, response, handler); } else if (ex instanceof BindException) { return handleBindException((BindException) ex, request, response, handler); } else if (ex instanceof NoHandlerFoundException) { return handleNoHandlerFoundException( (NoHandlerFoundException) ex, request, response, handler); } else if (ex instanceof AsyncRequestTimeoutException) { return handleAsyncRequestTimeoutException( (AsyncRequestTimeoutException) ex, request, response, handler); } } catch (Exception handlerEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", handlerEx); } } return null; } ``` #### DefaultHandlerExceptionResolver#handleMissingServletRequestParameter()展示tomcat默认错误页 ```java protected ModelAndView handleMissingServletRequestParameter(MissingServletRequestParameterException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { //直接 sendError tomcat展示错误页 response.sendError(HttpServletResponse.SC_BAD_REQUEST, ex.getMessage()); return new ModelAndView(); } ``` 这就是咱们刚刚报的错,缺少参数 #### 返回到DispatcherServlet#processHandlerException() 看到这里返回了一个空的ModelAndView,并不是NULL #### 返回到DispatcherServlet#processDispatchResult() 最后下面再处理下拦截器 最终结束 #### 页面效果 ### 传参但报错的情况 我们这样写:http://localhost:8080/springmvc_source_test/hello?name=zhangsan&i=0 > 前面讲的不再重复 #### DispatcherServlet#doDispatch() #### DispatcherServlet#processHandlerException() 到了第三个异常解析器也依然处理不了,于是出现了一个谁都处理不了的异常 然后就抛出此异常 #### 返回到DispatcherServlet#processDispatchResult()直接炸了 在这一步抛出了异常,整个方法直接炸了,后面的逻辑全都不走了 #### 返回到DispatcherServlet#doDispatch() #### DispatcherServlet#triggerAfterCompletion()抛异常 ```java private void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, Exception ex) throws Exception { if (mappedHandler != null) { mappedHandler.triggerAfterCompletion(request, response, ex); } throw ex; //抛出去 } ``` 执行完拦截器,异常继续往上抛,一层一层往上抛,最终抛给了tomcat #### 页面效果 这就是tomcat的默认错误页+堆栈页 ## 自定义异常处理 ### 测试类 #### InvalidUserException ```java @ResponseStatus(value = HttpStatus.CONFLICT, reason = "非法用户") public class InvalidUserException extends RuntimeException { private static final long serialVersionUID = -7034897190745766222L; } ``` #### HelloController ```java package cn.imlql.web.controller; @Controller public class HelloController { public HelloController(){ System.out.println("HelloController....."); } @GetMapping("/hello") public String sayHello(@RequestParam(name = "name",required = true) String name { if ("abc".equals(name)) { //非法的用户信息 throw new InvalidUserException(); } return "index.jsp"; } } ``` ### ResponseStatusExceptionResolver处理 ```java protected ModelAndView doResolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { if (ex instanceof ResponseStatusException) { return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler); } //拿到 ResponseStatus 注解 ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class); if (status != null) { return resolveResponseStatus(status, request, response, handler, ex); } if (ex.getCause() instanceof Exception) { return doResolveException(request, response, handler, (Exception) ex.getCause()); } } catch (Exception resolveEx) { if (logger.isWarnEnabled()) { logger.warn("Failure while trying to resolve exception [" + ex.getClass().getName() + "]", resolveEx); } } return null; } ``` ```java protected ModelAndView resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { //获取注解指定的响应状态码和错误原因 int statusCode = responseStatus.code().value(); String reason = responseStatus.reason(); return applyStatusAndReason(statusCode, reason, response); } ``` ```java protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) throws IOException { if (!StringUtils.hasLength(reason)) { response.sendError(statusCode); //返回默认错误页 } else { String resolvedReason = (this.messageSource != null ? this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) : reason); response.sendError(statusCode, resolvedReason); } return new ModelAndView(); } ``` 页面效果 ## 最常用的注解版异常解析器 @ExceptionHandler ### 测试类 #### MyExceptionHandler ```java @ControllerAdvice //专门处理所有controller异常的,它是一个复合注解,里面有@Component,所以默认加在容器中 public class MyExceptionHandler { @ResponseBody @ExceptionHandler(value = {ArithmeticException.class}) public String handleZeroException(Exception exception){ //参数位置 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler-args //返回值 https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-ann-exceptionhandler-return-values return "Error"; } } ``` 异常处理器能写这么多参数和返回值,也是用了参数解析器,返回值处理器 #### HelloController ```java package cn.imlql.web.controller; @Controller public class HelloController { public HelloController(){ System.out.println("HelloController....."); } @GetMapping("/hello") public String sayHello(Integer i) { int x = 10 / i; return "index.jsp"; } } ``` ### DispatcherServlet#doDispatch() ### ExceptionHandlerExceptionResolver#doResolveHandlerMethodException()寻找@ExceptionHandler注解标注的方法 ```java protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) { //为当前异常找一个处理方法??? @ExceptionHandler注解标注的方法 ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception); if (exceptionHandlerMethod == null) { return null; } //异常解析器里面 还是利用了以前的 argumentResolvers和 returnValueHandlers扩展了异常解析的功能 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 exceptions = new ArrayList<>(); try { if (logger.isDebugEnabled()) { logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod); } // Expose causes as provided arguments as well 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); // efficient arraycopy call in ArrayList arguments[arguments.length - 1] = handlerMethod; exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, arguments); } catch (Throwable invocationEx) { // Any other than the original exception (or a cause) is unintended here, // probably an accident (e.g. failed assertion or the like). if (!exceptions.contains(invocationEx) && 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 flashAttributes = ((RedirectAttributes) model).getFlashAttributes(); RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes); } return mav; } } ``` ### ExceptionHandlerExceptionResolver#getExceptionHandlerMethod() ### ExceptionHandlerMethodResolver#getMappedMethod() 最终调到这里 ```java private Method getMappedMethod(Class exceptionType) { List> matches = new ArrayList<>(); for (Class mappedException : this.mappedMethods.keySet()) { if (mappedException.isAssignableFrom(exceptionType)) { matches.add(mappedException); } } if (!matches.isEmpty()) { if (matches.size() > 1) { matches.sort(new ExceptionDepthComparator(exceptionType)); } return this.mappedMethods.get(matches.get(0)); } else { return NO_MATCHING_EXCEPTION_HANDLER_METHOD; } } ``` ### 返回到ExceptionHandlerExceptionResolver#doResolveHandlerMethodException() ### ServletInvocableHandlerMethod#invokeAndHandle() ### 小总结 1. 可以看到异常处理的方法和springmvc的普通Controller方法最终走到了相同的反射执行逻辑 2. 这也是为什么叫@ControllerAdvice,仅仅是Controller的增强 ### ExceptionHandlerExceptionResolver里的参数解析器和返回值解析器何时赋值? #### ExceptionHandlerExceptionResolver#afterPropertiesSet() ```java @Override //实现了 InitilazingBean 的组件,在容器创建完对象以后,会初始化调用 InitilazingBean public void afterPropertiesSet() { //初始化@ExceptionHandler 增强的缓存 Do this first, it may add ResponseBodyAdvice beans initExceptionHandlerAdviceCache(); //准备好异常解析用的参数解析器 if (this.argumentResolvers == null) { List resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { List handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } } ``` 还是咱们的老朋友InitializingBean,一样的逻辑,不讲了 #### ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache() ```java private void initExceptionHandlerAdviceCache() { if (getApplicationContext() == null) { return; } //找到所有的 ControllerAdviceBean(标注了@ControllerAdvice注解的类) 、 List 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); } } 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"); } } } ``` #### ControllerAdviceBean#findAnnotatedBeans()拿到所有组件看谁标注了 @ControllerAdvice ```java public static List findAnnotatedBeans(ApplicationContext context) { ListableBeanFactory beanFactory = context; if (context instanceof ConfigurableApplicationContext) { // Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory(); } List adviceBeans = new ArrayList<>(); for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) { if (!ScopedProxyUtils.isScopedTarget(name)) { //拿到所有组件看谁标注了 @ControllerAdvice ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class); if (controllerAdvice != null) { // Use the @ControllerAdvice annotation found by findAnnotationOnBean() // in order to avoid a subsequent lookup of the same annotation. adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice)); } } } OrderComparator.sort(adviceBeans); return adviceBeans; } ``` #### ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver() ```java public static final MethodFilter EXCEPTION_HANDLER_METHODS = method -> AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class); public ExceptionHandlerMethodResolver(Class handlerType) { //扫描当前这个ControllerAdvice中所有标注了@ExceptionHandler的方法 for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) { for (Class exceptionType : detectExceptionMappings(method)) { addExceptionMapping(exceptionType, method);// 每一个方法能处理什么异常类型,放入到map里 } } } ``` #### ExceptionHandlerMethodResolver#addExceptionMapping() ```java //每一个方法能处理什么异常类型,缓存到Map中。 private void addExceptionMapping(Class 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 + "}"); } } private final Map, Method> mappedMethods = new HashMap<>(16); ``` ### 小扩展 你也可以自己写一个异常解析器,比如@YouthExceptionHandler,这个注解将异常信息存档,比如存到hdfs,实时看报错日志。下面是思路: 1. 我们的@YouthExceptionHandler实现InitilazingBean接口,在PropertiesSet()时,分析所有标注了@YouthExceptionHandler注解的方法,在方法执行时进行hdfs存档 ## 进阶版@EnableWebMvc+WebMvcConfigurer启动Web功能 ### 概述 1. 在以前如果我们自定义自己的组件之后,DispatcherServlet就不再用内部提供的默认组件,导致我们失去了很多默认功能。 2. 比如在前面我们没讲的视图解析器,我在自己测试自定义视图解析器的时候,我发现只有自定义的视图解析器了,springmvc提供的默认视图解析器就没了。看下图 3. 我们最想要的结果就是既有我们自定义的组件,也有springmvc默认提供的组件。spring也提供了解决方法@EnableWebMvc+WebMvcConfigurer 4. 同时@EnableWebMvc+WebMvcConfigurer也使得扩展自定义组件变的很方便 ### 测试类-MvcExtendConfiguration ```java @EnableWebMvc //启用SpringMVC功能 @Configuration // public class MvcExtendConfiguration implements WebMvcConfigurer { public void configureViewResolvers(ViewResolverRegistry registry) { registry.viewResolver(new MeiNvViewResolver()); //不改源码就如下操作 InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix(""); viewResolver.setSuffix(".jsp"); //controller的返回值就不用写jsp registry.viewResolver(viewResolver); } } ``` WebMvcConfigurer就是给我们定制的,@EnableWebMvc就是启用默认的,下面讲讲原理 ### @EnableWebMvc+WebMvcConfigurer如何导入自定义组件 #### 注解@EnableWebMvc ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc { } ``` #### DelegatingWebMvcConfiguration ```java @Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { //这里是个组合关系 private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) //拿到容器中所有的 WebMvcConfigurer public void setConfigurers(List configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } } @Override protected void configureViewResolvers(ViewResolverRegistry registry) { this.configurers.configureViewResolvers(registry); } @Override protected void addArgumentResolvers(List argumentResolvers) { this.configurers.addArgumentResolvers(argumentResolvers); } @Override protected void addReturnValueHandlers(List returnValueHandlers) { this.configurers.addReturnValueHandlers(returnValueHandlers); } @Override protected void configureMessageConverters(List> converters) { this.configurers.configureMessageConverters(converters); } @Override protected void extendMessageConverters(List> converters) { this.configurers.extendMessageConverters(converters); } //...下面还有很多跟上面三个几乎一模一样的代码 } ``` 我们以ViewResolvers为例 #### WebMvcConfigurerComposite ```java @Override public void configureViewResolvers(ViewResolverRegistry registry) { for (WebMvcConfigurer delegate : this.delegates) { delegate.configureViewResolvers(registry); //最终这里会调到我们自定义的那个 } } @Override public void addArgumentResolvers(List argumentResolvers) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addArgumentResolvers(argumentResolvers); } } @Override public void addReturnValueHandlers(List returnValueHandlers) { for (WebMvcConfigurer delegate : this.delegates) { delegate.addReturnValueHandlers(returnValueHandlers); } } //...... ``` 1. 这里最终都会调用子类的实现 2. 那么@EnableWebMvc是如何导入很多默认组件。提供默认功能的呢?核心就是下面的WebMvcConfigurationSupport ### @EnableWebMvc是如何导入很多默认组件 #### WebMvcConfigurationSupport WebMvcConfigurationSupport是DelegatingWebMvcConfiguration的父类 ```java public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { @Bean public BeanNameUrlHandlerMapping beanNameHandlerMapping( @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) { //...... } @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) { //...... } @Bean public ViewResolver mvcViewResolver( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { //...... } @Bean @Lazy public HandlerMappingIntrospector mvcHandlerMappingIntrospector() { return new HandlerMappingIntrospector(); } @Bean public LocaleResolver localeResolver() { return new AcceptHeaderLocaleResolver(); } @Bean public ThemeResolver themeResolver() { return new FixedThemeResolver(); } @Bean public FlashMapManager flashMapManager() { return new SessionFlashMapManager(); } @Bean public RequestToViewNameTranslator viewNameTranslator() { return new DefaultRequestToViewNameTranslator(); } @Bean @SuppressWarnings("deprecation") public RequestMappingHandlerMapping requestMappingHandlerMapping(@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider){ RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping(); mapping.setOrder(0); mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider)); mapping.setContentNegotiationManager(contentNegotiationManager); mapping.setCorsConfigurations(getCorsConfigurations()); //... } //...... } ``` 1. 此类里面放了很多默认组件,包括九大组件还有很多我没列举出来的 #### WebMvcConfigurationSupport#getInterceptors() ```java protected final Object[] getInterceptors( FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { if (this.interceptors == null) { InterceptorRegistry registry = new InterceptorRegistry(); addInterceptors(registry); //子类模板先来修改 registry registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService)); registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider)); this.interceptors = registry.getInterceptors(); } return this.interceptors.toArray(); } ``` 2. 导入了这么多组件,这些组件在关键时刻会调用子类重写的方法,比如上面面的addInterceptors(registry);会先调用 子类`DelegatingWebMvcConfiguration#addInterceptors()` #### DelegatingWebMvcConfiguration#addInterceptors() ```java @Override protected void addInterceptors(InterceptorRegistry registry) { this.configurers.addInterceptors(registry); //如果我们继承了WebMvcConfigurer,并且重写了addInterceptors,这里就会加进来 } ``` 3. 也就是每一个组件的关键核心位置都留给了子类来重写 ### 为什么自定义视图解析器会覆盖默认的视图解析器? #### WebMvcConfigurationSupport#mvcViewResolver() ```java @Bean //给容器中放了视图解析器,容器中一开始就有mvcViewResolver这个bean的定义信息 public ViewResolver mvcViewResolver( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) { ViewResolverRegistry registry = new ViewResolverRegistry(contentNegotiationManager, this.applicationContext); configureViewResolvers(registry); //前面自己扩展配置视图解析器, //只有不自定义视图解析器,才会给容器放入默认视图解析器 if (registry.getViewResolvers().isEmpty() || this.applicationContext != null) { String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( //容器中只有一个组件的定义 this.applicationContext, ViewResolver.class, true, false); if (true || names.length == 1) { //容器中有视图解析器,会把 InternalResourceViewResolver 放在容器中 registry.getViewResolvers().add(new InternalResourceViewResolver()); //总是把默认的加入进去 } } //我们如果扩展配置了,完全按照我们configureViewResolvers,如果没有配置,用默认 ViewResolverComposite composite = new ViewResolverComposite(); composite.setOrder(registry.getOrder()); composite.setViewResolvers(registry.getViewResolvers()); if (this.applicationContext != null) { composite.setApplicationContext(this.applicationContext); } if (this.servletContext != null) { composite.setServletContext(this.servletContext); } return composite; } ``` 所以如果即想要自定义视图解析器,又想要默认的,就下面这样写 ```java @EnableWebMvc //启用SpringMVC功能 @Configuration // public class MvcExtendConfiguration implements WebMvcConfigurer { public void configureViewResolvers(ViewResolverRegistry registry) { registry.viewResolver(new MeiNvViewResolver()); //不改源码就如下操作 InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix(""); viewResolver.setSuffix(".jsp"); //controller的返回值就不用写jsp registry.viewResolver(viewResolver); } } ``` 其它组件可能也会有默认组件失效的情况,可以触类旁通看下源码 1. @EnableWebMvc导入的类会加入SpringMVC的很多核心默认组件,拥有默认功能 2. 这些默认组件在扩展的时候都是留给接口 WebMvcConfigurer(访问者模式,拿到真正的内容,比如上面的registry进行修改) 4、MeiNvViewResolver+InternalResourceViewResolver 5、@EnableWebMvc只是开启了SpringMVC最基本的功能,即使是以前自己也要配置默认视图解析器