Skip to content

Latest commit

 

History

History
1671 lines (944 loc) · 47 KB

11.第11章-SpringMVC异常处理源码和@EnableWebMvc原理.md

File metadata and controls

1671 lines (944 loc) · 47 KB

第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内全是异常处理环节
       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()准备处理异常

    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()解析异常

    @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()

    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) {
       //为当前异常找一个处理方法???  @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<Throwable> 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<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) {
          // 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<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 注解
          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 {  //处理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默认错误页

    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()抛异常

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 注解
          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  //专门处理所有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

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) {
       //为当前异常找一个处理方法???  @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<Throwable> 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<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()

小总结

  1. 可以看到异常处理的方法和springmvc的普通Controller方法最终走到了相同的反射执行逻辑
  2. 这也是为什么叫@ControllerAdvice,仅仅是Controller的增强

ExceptionHandlerExceptionResolver里的参数解析器和返回值解析器何时赋值?

ExceptionHandlerExceptionResolver#afterPropertiesSet()

    @Override  //实现了 InitilazingBean 的组件,在容器创建完对象以后,会初始化调用 InitilazingBean
    public void afterPropertiesSet() {
       //初始化@ExceptionHandler 增强的缓存   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);
       }
    }

还是咱们的老朋友InitializingBean,一样的逻辑,不讲了

ExceptionHandlerExceptionResolver#initExceptionHandlerAdviceCache()

    private void initExceptionHandlerAdviceCache() {
       if (getApplicationContext() == null) {
          return;
       }
       //找到所有的 ControllerAdviceBean(标注了@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 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) {
          // Use internal BeanFactory for potential downcast to ConfigurableBeanFactory above
          beanFactory = ((ConfigurableApplicationContext) context).getBeanFactory();
       }
       List<ControllerAdviceBean> 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()

    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<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
             addExceptionMapping(exceptionType, method);// 每一个方法能处理什么异常类型,放入到map里
          }
       }
    }

ExceptionHandlerMethodResolver#addExceptionMapping()

    //每一个方法能处理什么异常类型,缓存到Map中。
    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,实时看报错日志。下面是思路:

  1. 我们的@YouthExceptionHandler实现InitilazingBean接口,在PropertiesSet()时,分析所有标注了@YouthExceptionHandler注解的方法,在方法执行时进行hdfs存档

进阶版@EnableWebMvc+WebMvcConfigurer启动Web功能

概述

  1. 在以前如果我们自定义自己的组件之后,DispatcherServlet就不再用内部提供的默认组件,导致我们失去了很多默认功能。
  2. 比如在前面我们没讲的视图解析器,我在自己测试自定义视图解析器的时候,我发现只有自定义的视图解析器了,springmvc提供的默认视图解析器就没了。看下图

  1. 我们最想要的结果就是既有我们自定义的组件,也有springmvc默认提供的组件。spring也提供了解决方法@EnableWebMvc+WebMvcConfigurer
  2. 同时@EnableWebMvc+WebMvcConfigurer也使得扩展自定义组件变的很方便

测试类-MvcExtendConfiguration

@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

@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)  //拿到容器中所有的 WebMvcConfigurer
   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为例

WebMvcConfigurerComposite

    @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);
       }
    }

	//......
  1. 这里最终都会调用子类的实现
  2. 那么@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());
        
        //...
    }
    
    //......
    
}
  1. 此类里面放了很多默认组件,包括九大组件还有很多我没列举出来的

WebMvcConfigurationSupport#getInterceptors()

    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();
    }
  1. 导入了这么多组件,这些组件在关键时刻会调用子类重写的方法,比如上面面的addInterceptors(registry);会先调用

子类DelegatingWebMvcConfiguration#addInterceptors()

DelegatingWebMvcConfiguration#addInterceptors()

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
       this.configurers.addInterceptors(registry); //如果我们继承了WebMvcConfigurer,并且重写了addInterceptors,这里就会加进来
    }
  1. 也就是每一个组件的关键核心位置都留给了子类来重写

为什么自定义视图解析器会覆盖默认的视图解析器?

WebMvcConfigurationSupport#mvcViewResolver()

    @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;
    }

所以如果即想要自定义视图解析器,又想要默认的,就下面这样写

    @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最基本的功能,即使是以前自己也要配置默认视图解析器