第11章-SpringMVC异常处理源码和@EnableWebMvc原理 视图解析器不重要了,不细述了 因为现在都是前后端分离的架构,不太需要视图解析器了,有兴趣的可以自己研究。
异常处理流程 测试类 HelloController 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() 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 ); 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 ); } }
DispatcherServlet#processHandlerException()准备处理异常 protected ModelAndView processHandlerException (HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) throws Exception { request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); 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 ; } 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()解析异常 @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 ) { if (logger.isDebugEnabled() && (this .warnLogger == null || !this .warnLogger.isWarnEnabled())) { logger.debug("Resolved [" + ex + "]" + (result.isEmpty() ? "" : " to " + result)); } logException(ex, request); } return result; } else { return null ; } }
AbstractHandlerMethodExceptionResolver#doResolveException() 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注解标注的方法 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(); 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; } }
ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()遍历所有的@ControllerAdvice,看哪个类的方法能处理这个异常 protected ServletInvocableHandlerMethod getExceptionHandlerMethod ( @Nullable HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = null ; if (handlerMethod != null ) { 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); } 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); } } } return null ; }
返回到ExceptionHandlerExceptionResolver#doResolveHandlerMethodException() 因为咱们没有@ControllerAdvice标注的类,所以这里会返回空
接着返回
返回到DispatcherServlet#processHandlerException() 准备循环第二个异常解析器
接下来还是父类AbstractHandlerMethodExceptionResolver那个模板方法,前面写了,这里直接跳过
ResponseStatusExceptionResolver#doResolveException()处理@ResponseStatus注解标注的相关异常 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 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底层的异常 @Override @Nullable protected ModelAndView doResolveException ( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { try { 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默认错误页 protected ModelAndView handleMissingServletRequestParameter (MissingServletRequestParameterException ex, HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException { 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()抛异常 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 @ResponseStatus(value = HttpStatus.CONFLICT, reason = "非法用户") public class InvalidUserException extends RuntimeException { private static final long serialVersionUID = -7034897190745766222L ; }
HelloController 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处理 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 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 ; }
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); }
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 @ControllerAdvice public class MyExceptionHandler { @ResponseBody @ExceptionHandler(value = {ArithmeticException.class}) public String handleZeroException (Exception exception) { return "Error" ; } }
异常处理器能写这么多参数和返回值,也是用了参数解析器,返回值处理器
HelloController 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注解标注的方法 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(); 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; } }
ExceptionHandlerExceptionResolver#getExceptionHandlerMethod()
ExceptionHandlerMethodResolver#getMappedMethod() 最终调到这里
private Method getMappedMethod (Class<? extends Throwable> exceptionType) { List<Class<? extends Throwable>> matches = new ArrayList<>(); for (Class<? extends Throwable> 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()
小总结
可以看到异常处理的方法和springmvc的普通Controller方法最终走到了相同的反射执行逻辑
这也是为什么叫@ControllerAdvice,仅仅是Controller的增强
ExceptionHandlerExceptionResolver里的参数解析器和返回值解析器何时赋值? ExceptionHandlerExceptionResolver#afterPropertiesSet() @Override public void afterPropertiesSet () { 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); } }
还是咱们的老朋友InitializingBean,一样的逻辑,不讲了
ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache() private void initExceptionHandlerAdviceCache () { if (getApplicationContext() == null ) { return ; } 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); } } 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 public static List<ControllerAdviceBean> findAnnotatedBeans (ApplicationContext context) { ListableBeanFactory beanFactory = context; if (context instanceof ConfigurableApplicationContext) { beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory(); } List<ControllerAdviceBean> adviceBeans = new ArrayList<>(); for (String name : BeanFactoryUtils.beanNamesForTypeIncludingAncestors(beanFactory, Object.class)) { if (!ScopedProxyUtils.isScopedTarget(name)) { ControllerAdvice controllerAdvice = beanFactory.findAnnotationOnBean(name, ControllerAdvice.class); if (controllerAdvice != null ) { adviceBeans.add(new ControllerAdviceBean(name, beanFactory, controllerAdvice)); } } } OrderComparator.sort(adviceBeans); return adviceBeans; }
ExceptionHandlerMethodResolver#ExceptionHandlerMethodResolver() 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); } } }
ExceptionHandlerMethodResolver#addExceptionMapping() 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 + "}" ); } } private final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16 );
小扩展 你也可以自己写一个异常解析器,比如@YouthExceptionHandler,这个注解将异常信息存档,比如存到hdfs,实时看报错日志。下面是思路:
我们的@YouthExceptionHandler实现InitilazingBean接口,在PropertiesSet()时,分析所有标注了@YouthExceptionHandler注解的方法,在方法执行时进行hdfs存档
概述
在以前如果我们自定义自己的组件之后,DispatcherServlet就不再用内部提供的默认组件,导致我们失去了很多默认功能。
比如在前面我们没讲的视图解析器,我在自己测试自定义视图解析器的时候,我发现只有自定义的视图解析器了,springmvc提供的默认视图解析器就没了。看下图
我们最想要的结果就是既有我们自定义的组件,也有springmvc默认提供的组件。spring也提供了解决方法@EnableWebMvc+WebMvcConfigurer
同时@EnableWebMvc+WebMvcConfigurer也使得扩展自定义组件变的很方便
测试类-MvcExtendConfiguration @EnableWebMvc @Configuration public class MvcExtendConfiguration implements WebMvcConfigurer { public void configureViewResolvers (ViewResolverRegistry registry) { registry.viewResolver(new MeiNvViewResolver()); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("" ); viewResolver.setSuffix(".jsp" ); registry.viewResolver(viewResolver); } }
WebMvcConfigurer就是给我们定制的,@EnableWebMvc就是启用默认的,下面讲讲原理
注解@EnableWebMvc @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented @Import(DelegatingWebMvcConfiguration.class) public @interface EnableWebMvc {}
DelegatingWebMvcConfiguration @Configuration(proxyBeanMethods = false) public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); @Autowired(required = false) public void setConfigurers (List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this .configurers.addWebMvcConfigurers(configurers); } } @Override protected void configureViewResolvers (ViewResolverRegistry registry) { this .configurers.configureViewResolvers(registry); } @Override protected void addArgumentResolvers (List<HandlerMethodArgumentResolver> argumentResolvers) { this .configurers.addArgumentResolvers(argumentResolvers); } @Override protected void addReturnValueHandlers (List<HandlerMethodReturnValueHandler> returnValueHandlers) { this .configurers.addReturnValueHandlers(returnValueHandlers); } @Override protected void configureMessageConverters (List<HttpMessageConverter<?>> converters) { this .configurers.configureMessageConverters(converters); } @Override protected void extendMessageConverters (List<HttpMessageConverter<?>> converters) { this .configurers.extendMessageConverters(converters); } }
我们以ViewResolvers为例
@Override public void configureViewResolvers (ViewResolverRegistry registry) { for (WebMvcConfigurer delegate : this .delegates) { delegate.configureViewResolvers(registry); } } @Override public void addArgumentResolvers (List<HandlerMethodArgumentResolver> argumentResolvers) { for (WebMvcConfigurer delegate : this .delegates) { delegate.addArgumentResolvers(argumentResolvers); } } @Override public void addReturnValueHandlers (List<HandlerMethodReturnValueHandler> returnValueHandlers) { for (WebMvcConfigurer delegate : this .delegates) { delegate.addReturnValueHandlers(returnValueHandlers); } }
这里最终都会调用子类的实现
那么@EnableWebMvc是如何导入很多默认组件。提供默认功能的呢?核心就是下面的WebMvcConfigurationSupport
@EnableWebMvc是如何导入很多默认组件 WebMvcConfigurationSupport WebMvcConfigurationSupport是DelegatingWebMvcConfiguration的父类
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()); } }
此类里面放了很多默认组件,包括九大组件还有很多我没列举出来的
WebMvcConfigurationSupport#getInterceptors() protected final Object[] getInterceptors( FormattingConversionService mvcConversionService, ResourceUrlProvider mvcResourceUrlProvider) { if (this .interceptors == null ) { InterceptorRegistry registry = new InterceptorRegistry(); addInterceptors(registry); registry.addInterceptor(new ConversionServiceExposingInterceptor(mvcConversionService)); registry.addInterceptor(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider)); this .interceptors = registry.getInterceptors(); } return this .interceptors.toArray(); }
导入了这么多组件,这些组件在关键时刻会调用子类重写的方法,比如上面面的addInterceptors(registry);会先调用
子类DelegatingWebMvcConfiguration#addInterceptors()
DelegatingWebMvcConfiguration#addInterceptors() @Override protected void addInterceptors (InterceptorRegistry registry) { this .configurers.addInterceptors(registry); }
也就是每一个组件的关键核心位置都留给了子类来重写
为什么自定义视图解析器会覆盖默认的视图解析器? WebMvcConfigurationSupport#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 ) { registry.getViewResolvers().add(new InternalResourceViewResolver()); } } 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; }
所以如果即想要自定义视图解析器,又想要默认的,就下面这样写
@EnableWebMvc @Configuration public class MvcExtendConfiguration implements WebMvcConfigurer { public void configureViewResolvers (ViewResolverRegistry registry) { registry.viewResolver(new MeiNvViewResolver()); InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setPrefix("" ); viewResolver.setSuffix(".jsp" ); registry.viewResolver(viewResolver); } }
其它组件可能也会有默认组件失效的情况,可以触类旁通看下源码
@EnableWebMvc导入的类会加入SpringMVC的很多核心默认组件,拥有默认功能
这些默认组件在扩展的时候都是留给接口 WebMvcConfigurer(访问者模式,拿到真正的内容,比如上面的registry进行修改) 4、MeiNvViewResolver+InternalResourceViewResolver 5、@EnableWebMvc只是开启了SpringMVC最基本的功能,即使是以前自己也要配置默认视图解析器