HandlerExceptionResolver 组件

HandlerExceptionResolver 组件,处理器异常解析器,将处理器( handler )执行时发生的异常(也就是处理请求,执行方法的过程中)解析(转换)成对应的 ModelAndView 结果

回顾

先来回顾一下在 DispatcherServlet 中处理请求的过程中哪里使用到 HandlerExceptionResolver 组件,可以回到《一个请求的旅行过程》中的 DispatcherServlet 的 processHandlerException 方法中看看,如下:


@Nullable
protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
        @Nullable Object handler, Exception ex) throws Exception {

    // Success and error responses may use different content types
    // 移除 PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE 属性
    request.removeAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);

    // Check registered HandlerExceptionResolvers...
    // <a> 遍历 HandlerExceptionResolver 数组,解析异常,生成 ModelAndView 对象
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        // 遍历 HandlerExceptionResolver 数组
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            // 解析异常,生成 ModelAndView 对象
            exMv = resolver.resolveException(request, response, handler, ex);
            // 生成成功,结束循环
            if (exMv != null) {
                break;
            }
        }
    }
    // <b> 情况一,生成了 ModelAndView 对象,进行返回
    if (exMv != null) {
        // ModelAndView 对象为空,则返回 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);
            }
        }
        // 设置请求中的错误消息属性
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }
    // <c> 情况二,未生成 ModelAndView 对象,则抛出异常
    throw ex;
}

在 Spring MVC 的 DispatcherServlet 处理请求执行方法过程中,不管是否抛出异常都会进行结果处理,如果抛出了异常也需要调用该方法处理异常

可以看到,在 <a> 处会遍历所有的 HandlerExceptionResolver 异常处理器来处理,如果某一个处理器处理成功并返回 ModelAndView 对象,则直接返回

HandlerExceptionResolver 接口

org.springframework.web.servlet.HandlerExceptionResolver,异常处理器接口,代码如下:


public interface HandlerExceptionResolver {
	/**
	 * 解析异常,转换成对应的 ModelAndView 结果
	 */
	@Nullable
	ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

HandlerExceptionResolver 接口体系的结构如下:

Spring MVC 源码分析 - HandlerExceptionResolver 组件_mvc

初始化过程

在 DispatcherServlet 的 initHandlerExceptionResolvers(ApplicationContext context) 方法,初始化 HandlerExceptionResolver 组件,方法如下:


private void initHandlerExceptionResolvers(ApplicationContext context) {
    // 置空 handlerExceptionResolvers 处理
    this.handlerExceptionResolvers = null;

    // 情况一,自动扫描 HandlerExceptionResolver 类型的 Bean 们
    if (this.detectAllHandlerExceptionResolvers) {
        // Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
        Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
                .beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
        if (!matchingBeans.isEmpty()) {
            this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
            // We keep HandlerExceptionResolvers in sorted order.
            AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
        }
    }
    // 情况二,获得名字为 HANDLER_EXCEPTION_RESOLVER_BEAN_NAME 的 Bean
    else {
        try {
            HandlerExceptionResolver her = context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
            this.handlerExceptionResolvers = Collections.singletonList(her);
        }
        catch (NoSuchBeanDefinitionException ex) {
            // Ignore, no HandlerExceptionResolver is fine too.
        }
    }

    // Ensure we have at least some HandlerExceptionResolvers, by registering
    // default HandlerExceptionResolvers if no other resolvers are found.
    /**
     * 情况三,如果未获得到,则获得默认配置的 HandlerExceptionResolver 类
     * {@link org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver}
     * {@link	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver}
     * {@link	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver}
     */
    if (this.handlerExceptionResolvers == null) {
        this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
        if (logger.isTraceEnabled()) {
            logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
                    "': using default strategies from DispatcherServlet.properties");
        }
    }
}
  1. 如果“开启”探测功能,则扫描已注册的 HandlerExceptionResolver 的 Bean 们,添加到 handlerExceptionResolvers 中,默认开启
  2. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerExceptionResolver" 对应的 Bean ,将其添加至 handlerExceptionResolvers
  3. 如果未获得到,则获得默认配置的 HandlerExceptionResolver 类,调用 getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) 方法,就是从 DispatcherServlet.properties 文件中读取 HandlerExceptionResolver 的默认实现类,如下:
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
	org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
	org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

在 Spring Boot 中,默认配置下会走上述 1 的逻辑,handlerExceptionResolvers 有两个元素:

  • org.springframework.boot.autoconfigure.web.DefaultErrorAttributes:在 Spring Boot 中,逻辑比较简单,暂时忽略
  • org.springframework.web.servlet.handler.HandlerExceptionResolverComposite:复合的 HandlerExceptionResolver 实现类

Spring MVC 源码分析 - HandlerExceptionResolver 组件_mvc_02

接下来会对 HandlerExceptionResolverComposite 中的这三种异常处理器进行分析

HandlerExceptionResolverComposite

org.springframework.web.servlet.handler.HandlerExceptionResolverComposite,实现 HandlerExceptionResolver、Ordered 接口,复合的 HandlerExceptionResolver 实现类

构造方法


public class HandlerExceptionResolverComposite implements HandlerExceptionResolver, Ordered {
	/**
	 * 异常解析器数组
	 */
	@Nullable
	private List<HandlerExceptionResolver> resolvers;
	/**
	 * 优先级,默认最低
	 */
	private int order = Ordered.LOWEST_PRECEDENCE;
}
  • resolvers:HandlerExceptionResolver 实现类列表
  • order:优先级,默认最低

从上面的初始化过程中可以看到,Spring Boot 默认配置下 HandlerExceptionResolverComposite 包含三个实现类:

  1. org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver
  2. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver
  3. org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

resolveException

实现 resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 方法,遍历 HandlerExceptionResolver 数组,逐个处理异常 ex,如果成功,则返回 ModelAndView 对象,方法如下:


@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response,
                                     @Nullable Object handler, Exception ex) {
    if (this.resolvers != null) {
        for (HandlerExceptionResolver handlerExceptionResolver : this.resolvers) {
            ModelAndView mav = handlerExceptionResolver.resolveException(request, response, handler, ex);
            if (mav != null) {
                return mav;
            }
        }
    }
    return null;
}

AbstractHandlerExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver,实现 HandlerExceptionResolver、Ordered 接口,HandlerExceptionResolver 抽象类,作为所有 HandlerExceptionResolver 实现类的基类

构造方法


public abstract class AbstractHandlerExceptionResolver implements HandlerExceptionResolver, Ordered {

	private static final String HEADER_CACHE_CONTROL = "Cache-Control";
	/**
	 * 优先级,默认最低
	 */
	private int order = Ordered.LOWEST_PRECEDENCE;
	/**
	 * 匹配的处理器对象的集合
	 */
	@Nullable
	private Set<?> mappedHandlers;
	/**
	 * 匹配的处理器类型的数组
	 */
	@Nullable
	private Class<?>[] mappedHandlerClasses;
	/**
	 * 防止响应缓存
	 */
	private boolean preventResponseCaching = false;
}

上面的这些属性在后续方法中会讲到

shouldApplyTo

shouldApplyTo(HttpServletRequest request, Object handler) 方法,判断当前 HandlerExceptionResolver 是否能应用到传入的 handler 处理器,方法如下:


protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    if (handler != null) {
        // <1> 如果 mappedHandlers 包含 handler 对象,则返回 true
        if (this.mappedHandlers != null && this.mappedHandlers.contains(handler)) {
            return true;
        }
        // <2> 如果 mappedHandlerClasses 包含 handler 的类型,则返回 true
        if (this.mappedHandlerClasses != null) {
            for (Class<?> handlerClass : this.mappedHandlerClasses) {
                if (handlerClass.isInstance(handler)) {
                    return true;
                }
            }
        }
    }
    // Else only apply if there are no explicit handler mappings.
    // <3> 如果 mappedHandlers 和 mappedHandlerClasses 都为空,说明直接匹配
    return (this.mappedHandlers == null && this.mappedHandlerClasses == null);
}
  1. 如果 mappedHandlers 包含该 handler 处理器对象,则返回 true
  2. 如果 mappedHandlerClasses 包含该 handler 处理器所在类,则返回 true
  3. 如果 mappedHandlers 和 mappedHandlerClasses 都为空,说明直接匹配

prepareResponse

prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止响应缓存,方法如下:


protected void prepareResponse(Exception ex, HttpServletResponse response) {
    if (this.preventResponseCaching) {
        preventCaching(response);
    }
}

/**
 * Prevents the response from being cached, through setting corresponding
 * HTTP {@code Cache-Control: no-store} header.
 * @param response current HTTP response
 */
protected void preventCaching(HttpServletResponse response) {
    response.addHeader(HEADER_CACHE_CONTROL, "no-store");
}

如果想要阻止响应缓存,需要设置 preventResponseCaching 为 true

resolveException

实现 resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:


@Override
@Nullable
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, 
                                     @Nullable Object handler, Exception ex) {
    // <1> 判断是否可以应用
    if (shouldApplyTo(request, handler)) {
        // <1.1> 阻止缓存
        prepareResponse(ex, response);
        // <1.2> 执行解析异常,返回 ModelAndView 对象
        ModelAndView result = doResolveException(request, response, handler, ex);
        // <1.3> 如果 ModelAndView 对象非空,则打印日志
        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);
        }
        // <1.4> 返回执行结果
        return result;
    }
    // <2> 不可应用,直接返回 null
    else {
        return null;
    }
}

@Nullable
protected abstract ModelAndView doResolveException(
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
  1. 调用 shouldApplyTo(HttpServletRequest request, Object handler) 方法,判断是否可以应用,如果可以应用
  1. 调用 prepareResponse(Exception ex, HttpServletResponse response) 方法,阻止缓存
  2. 调用 doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 抽象方法,执行解析异常,返回 ModelAndView 对象
  3. 如果 ModelAndView 对象非空,则打印日志
  4. 返回执行结果
  1. 不可应用,直接返回 null

AbstractHandlerMethodExceptionResolver

org.springframework.web.servlet.handler.AbstractHandlerMethodExceptionResolver,继承 AbstractHandlerExceptionResolver 抽象类,基于 handler 类型为 HandlerMethod 的 HandlerExceptionResolver 抽象类。

可能你会有疑惑,为什么 AbstractHandlerMethodExceptionResolver 只有一个 ExceptionHandlerExceptionResolver 子类,为什么还要做抽象呢?因为 ExceptionHandlerExceptionResolver 是基于 @ExceptionHandler 注解来配置对应的异常处理器,而如果未来我们想自定义其它的方式来配置对应的异常处理器,就可以来继承 AbstractHandlerMethodExceptionResolver 这个抽象类。😈

有没发现 Spring MVC 中,存在大量的逻辑与配置分离的分层实现,嘻嘻~:happy:

shouldApplyTo

重写 shouldApplyTo(HttpServletRequest request, Object handler) 方法,代码如下:


@Override
protected boolean shouldApplyTo(HttpServletRequest request, @Nullable Object handler) {
    // 情况一,如果 handler 为空,则直接调用父方法
    if (handler == null) {
        return super.shouldApplyTo(request, null);
    }
    // 情况二,处理 handler 为 HandlerMethod 类型的情况
    else if (handler instanceof HandlerMethod) {
        // <x> 获得真正的 handler
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        handler = handlerMethod.getBean();
        // 调用父方法
        return super.shouldApplyTo(request, handler);
    }
    // 情况三,直接返回 false
    else {
        return false;
    }
}

重点在于情况二,需要在 <x> 处,调用 HandlerMethod#getBean() 方法,获得真正的 handler 处理器。

doResolveException

重写 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:


@Override
@Nullable
protected final ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, 
                                                @Nullable Object handler, Exception ex) {
    return doResolveHandlerMethodException(request, response, (HandlerMethod) handler, ex);
}

@Nullable
protected abstract ModelAndView doResolveHandlerMethodException(HttpServletRequest request, HttpServletResponse response, 
                                                                @Nullable HandlerMethod handlerMethod, Exception ex);

将 handler 转换成 HandlerMethod 类型,并提供新的抽象方法

【重点】ExceptionHandlerExceptionResolver

org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,实现 ApplicationContextAware、InitializingBean 接口,继承 AbstractHandlerMethodExceptionResolver 抽象类,基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。

示例

可能你没有使用 @ExceptionHandler 注解来实现过异常的处理,例如:


@Log4j2
@RestControllerAdvice
public class CustomizeExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler({EmptyArgumentException.class, IllegalArgumentException.class})
    public Result<?> customizeHandleArgumentException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        response.setStatus(HttpStatus.OK.value());
        return Result.fail(ResultCode.PARAM_ERROR.getCode(), e.getMessage());
    }

    @ExceptionHandler({Exception.class})
    public Result<?> customizeHandleException(HttpServletRequest request, final Exception e, HttpServletResponse response) {
        log.error("异常拦截[{}]:", e.getMessage(), e);
        response.setStatus(HttpStatus.OK.value());
        return Result.fail(ResultCode.UNKNOWN.getCode(), e.getMessage());
    }
}

该自定义异常处理类会处理 Controller 类抛出的指定类型的异常

构造方法


public class ExceptionHandlerExceptionResolver extends AbstractHandlerMethodExceptionResolver
		implements ApplicationContextAware, InitializingBean {
	/**
	 * 自定义的方法参数处理器
	 */
	@Nullable
	private List<HandlerMethodArgumentResolver> customArgumentResolvers;
	/**
	 * 方法参数处理器组合
	 */
	@Nullable
	private HandlerMethodArgumentResolverComposite argumentResolvers;
	/**
	 * 自定义的执行结果处理器
	 */
	@Nullable
	private List<HandlerMethodReturnValueHandler> customReturnValueHandlers;
	/**
	 * 执行结果处理器组合
	 */
	@Nullable
	private HandlerMethodReturnValueHandlerComposite returnValueHandlers;
	/*
	 * HTTP 消息转换器
	 */
	private List<HttpMessageConverter<?>> messageConverters;

	private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager();
	/**
	 * 响应体的后置增强器
	 */
	private final List<Object> responseBodyAdvice = new ArrayList<>();

	@Nullable
	private ApplicationContext applicationContext;

	private final Map<Class<?>, ExceptionHandlerMethodResolver> exceptionHandlerCache = new ConcurrentHashMap<>(64);
	private final Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = new LinkedHashMap<>();

	public ExceptionHandlerExceptionResolver() {
		StringHttpMessageConverter stringHttpMessageConverter = new StringHttpMessageConverter();
		stringHttpMessageConverter.setWriteAcceptCharset(false);  // see SPR-7316
		// 初始化 messageConverters
		this.messageConverters = new ArrayList<>();
		this.messageConverters.add(new ByteArrayHttpMessageConverter());
		this.messageConverters.add(stringHttpMessageConverter);
		try {
			this.messageConverters.add(new SourceHttpMessageConverter<>());
		} catch (Error err) {
			// Ignore when no TransformerFactory implementation is available
		}
		this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
	}
}

有没有一种熟悉的感觉,和 《HandlerAdapter 组件(一)之 HandlerAdapter》 的 RequestMappingHandlerAdapter 类似,有大量的相同变量,例如参数解析器和返回结果处理器,最终也是调用 ServletInvocableHandlerMethod 的方法。因为你定义也是定义的方法去处理相关的异常😈 往下看

afterPropertiesSet

因为 ExceptionHandlerExceptionResolver 实现了 InitializingBean 接口,在 Sping 初始化该 Bean 的时候,会调用该方法,完成一些初始化工作,方法如下:


@Override
public void afterPropertiesSet() {
    // Do this first, it may add ResponseBodyAdvice beans
    // 初始化 exceptionHandlerAdviceCache、responseBodyAdvice
    initExceptionHandlerAdviceCache();

    // 初始化 argumentResolvers 参数
    if (this.argumentResolvers == null) {
        List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
        this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
    }
    // 初始化 returnValueHandlers 参数
    if (this.returnValueHandlers == null) {
        List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
        this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
    }
}
  1. 调用 initExceptionHandlerAdviceCache() 方法,初始化 exceptionHandlerAdviceCacheresponseBodyAdvice,详情见下文
  2. 初始化 argumentResolvers 属性。其中,#getDefaultArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,详情见下文
  3. 初始化 returnValueHandlers 属性。其中,#getDefaultReturnValueHandlers() 方法,获得默认的 HandlerMethodReturnValueHandler 数组,详情见下文

initExceptionHandlerAdviceCache

initExceptionHandlerAdviceCache() 方法,初始化 exceptionHandlerAdviceCacheresponseBodyAdvice,方法如下:


private void initExceptionHandlerAdviceCache() {
    if (getApplicationContext() == null) {
        return;
    }

    // <1> 扫描 @ControllerAdvice 注解的 Bean 们,并将进行排序
    List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
    AnnotationAwareOrderComparator.sort(adviceBeans);

    // <2> 遍历 ControllerAdviceBean 数组
    for (ControllerAdviceBean adviceBean : adviceBeans) {
        Class<?> beanType = adviceBean.getBeanType();
        if (beanType == null) {
            throw new IllegalStateException("Unresolvable type for ControllerAdviceBean: " + adviceBean);
        }
        // <2.1> 扫描该 ControllerAdviceBean 对应的类型
        ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(beanType);
        // <2.2> 有 @ExceptionHandler 注解,则添加到 exceptionHandlerAdviceCache 中
        if (resolver.hasExceptionMappings()) {
            this.exceptionHandlerAdviceCache.put(adviceBean, resolver);
        }
        // <2.3> 如果该 beanType 类型是 ResponseBodyAdvice 子类,则添加到 responseBodyAdvice 中
        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");
        }
    }
}
  1. 调用 ControllerAdviceBean 的 findAnnotatedBeans(ApplicationContext context) 方法,扫描 @ControllerAdvice 注解的 Bean 们,并将进行排序,这里就会扫描到上面示例中 CustomizeExceptionHandler 自定义异常处理类
  2. 遍历 ControllerAdviceBean 数组
  1. 创建扫描该 ControllerAdviceBean 对应的类型 ExceptionHandlerMethodResolver 对象 resolver,该对象在下面会分析
  2. 有 @ExceptionHandler 注解,则将resolver添加到 exceptionHandlerAdviceCache 中
  3. 如果该 beanType 类型是 ResponseBodyAdvice 子类,则添加到 responseBodyAdvice 中

getDefaultArgumentResolvers

getDefaultArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,方法如下:


protected List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
    List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>();

    // Annotation-based argument resolution
    resolvers.add(new SessionAttributeMethodArgumentResolver());
    resolvers.add(new RequestAttributeMethodArgumentResolver());

    // Type-based argument resolution
    resolvers.add(new ServletRequestMethodArgumentResolver());
    resolvers.add(new ServletResponseMethodArgumentResolver());
    resolvers.add(new RedirectAttributesMethodArgumentResolver());
    resolvers.add(new ModelMethodProcessor());

    // Custom arguments
    if (getCustomArgumentResolvers() != null) {
        resolvers.addAll(getCustomArgumentResolvers());
    }

    return resolvers;
}

getDefaultReturnValueHandlers

getDefaultReturnValueHandlers() 方法,获得默认的 HandlerMethodReturnValueHandler 数组,方法如下:


protected List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
    List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

    // Single-purpose return value types
    handlers.add(new ModelAndViewMethodReturnValueHandler());
    handlers.add(new ModelMethodProcessor());
    handlers.add(new ViewMethodReturnValueHandler());
    handlers.add(new HttpEntityMethodProcessor(
            getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));

    // Annotation-based return value types
    handlers.add(new ModelAttributeMethodProcessor(false));
    handlers.add(new RequestResponseBodyMethodProcessor(
            getMessageConverters(), this.contentNegotiationManager, this.responseBodyAdvice));

    // Multi-purpose return value types
    handlers.add(new ViewNameMethodReturnValueHandler());
    handlers.add(new MapMethodProcessor());

    // Custom return value types
    if (getCustomReturnValueHandlers() != null) {
        handlers.addAll(getCustomReturnValueHandlers());
    }

    // Catch-all
    handlers.add(new ModelAttributeMethodProcessor(true));

    return handlers;
}

ExceptionHandlerMethodResolver 类

在 ExceptionHandlerExceptionResolver 的 initExceptionHandlerAdviceCache 方法中会用到,两者的名字太容易混淆了

org.springframework.web.method.annotation.ExceptionHandlerMethodResolver,添加 @ControllerAdvice 注解的 Bean,用于解析添加了 @ExceptionHandler 注解的方法

构造方法


public class ExceptionHandlerMethodResolver {
	/**
	 * A filter for selecting {@code @ExceptionHandler} methods.
	 * 
	 * MethodFilter 对象,用于过滤带有 @ExceptionHandler 注解的方法
	 */
	public static final MethodFilter EXCEPTION_HANDLER_METHODS = method ->
			AnnotatedElementUtils.hasAnnotation(method, ExceptionHandler.class);
	/**
	 * 已经映射的方法
	 *
	 * 在 {@link #ExceptionHandlerMethodResolver(Class)} 构造方法中初始化
	 */
	pivate final Map<Class<? extends Throwable>, Method> mappedMethods = new HashMap<>(16);
	/**
	 * 已经匹配的方法
	 *
	 * 在 {@link #resolveMethod(Exception)} 方法中初始化
	 */
	private final Map<Class<? extends Throwable>, Method> exceptionLookupCache = new ConcurrentReferenceHashMap<>(16);

	public ExceptionHandlerMethodResolver(Class<?> handlerType) {
		// <1> 遍历 @ExceptionHandler 注解的方法,这些方法用于处理对应的异常
		for (Method method : MethodIntrospector.selectMethods(handlerType, EXCEPTION_HANDLER_METHODS)) {
			// <2> 遍历处理的异常集合,获取到该方法能处理哪些异常
			for (Class<? extends Throwable> exceptionType : detectExceptionMappings(method)) {
				// <3> 添加到 mappedMethods 中
				addExceptionMapping(exceptionType, method);
			}
		}
	}
}

mappedMethods 和 exceptionLookupCache 差别在于,后者是经过查找,比较优先级之后所产生的

  1. 遍历 @ExceptionHandler 注解的方法
  2. 调用 detectExceptionMappings(Method method) 方法,获得方法的异常数组,如下:
private List<Class<? extends Throwable>> detectExceptionMappings(Method method) {
    List<Class<? extends Throwable>> result = new ArrayList<>();
    // 首先,从方法上的 @ExceptionHandler 注解中,获得要处理的异常类型,添加到 result 中
    detectAnnotationExceptionMappings(method, result);
    // 其次,如果获取不到,从方法参数中,获得所处理的异常,添加到 result 中
    if (result.isEmpty()) {
        for (Class<?> paramType : method.getParameterTypes()) {
            if (Throwable.class.isAssignableFrom(paramType)) {
                result.add((Class<? extends Throwable>) paramType);
            }
        }
    }
    // 如果获取不到,则抛出 IllegalStateException 异常
    if (result.isEmpty()) {
        throw new IllegalStateException("No exception types mapped to " + method);
    }
    return result;
}

private void detectAnnotationExceptionMappings(Method method, List<Class<? extends Throwable>> result) {
    ExceptionHandler ann = AnnotatedElementUtils.findMergedAnnotation(method, ExceptionHandler.class);
    Assert.state(ann != null, "No ExceptionHandler annotation");
    result.addAll(Arrays.asList(ann.value()));
}
  1. 调用 addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) 方法,添加到 mappedMethods 中,如下:
private void addExceptionMapping(Class<? extends Throwable> exceptionType, Method method) {
    // 添加到 mappedMethods 中
    Method oldMethod = this.mappedMethods.put(exceptionType, method);
    // 如果已存在,说明冲突,所以抛出 IllegalStateException 异常
    if (oldMethod != null && !oldMethod.equals(method)) {
        throw new IllegalStateException("Ambiguous @ExceptionHandler method mapped for [" +
                exceptionType + "]: {" + oldMethod + ", " + method + "}");
    }
}
hasExceptionMappings

hasExceptionMappings() 方法,判断 mappedMethods 非空,方法如下:


public boolean hasExceptionMappings() {
    return !this.mappedMethods.isEmpty();
}
resolveMethod

resolveMethod(Exception exception) 方法,获取解析异常对应的方法,方法如下:


@Nullable
public Method resolveMethod(Exception exception) {
    return resolveMethodByThrowable(exception);
}
@Nullable
public Method resolveMethodByThrowable(Throwable exception) {
    // 首先,获得异常对应的方法
    Method method = resolveMethodByExceptionType(exception.getClass());
    // 其次,获取不到,则使用异常 cause 对应的方法
    if (method == null) {
        Throwable cause = exception.getCause();
        if (cause != null) {
            method = resolveMethodByExceptionType(cause.getClass());
        }
    }
    return method;
}

按照 exception 和 exception.cause 的先后,调用 resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) 方法,获得异常对应的方法,如下:


@Nullable
public Method resolveMethodByExceptionType(Class<? extends Throwable> exceptionType) {
    // 首先,先从 exceptionLookupCache 缓存中获得异常对应的处理方法
    Method method = this.exceptionLookupCache.get(exceptionType);
    // 其次,获取不到,则从 mappedMethods 中获得,并添加到 exceptionLookupCache 中
    if (method == null) {
        method = getMappedMethod(exceptionType);
        this.exceptionLookupCache.put(exceptionType, method);
    }
    return method;
}

逻辑比较简单,调用 getMappedMethod(Class<? extends Throwable> exceptionType) 方法,获得异常对应的方法,如下:


@Nullable
private Method getMappedMethod(Class<? extends Throwable> exceptionType) {
    List<Class<? extends Throwable>> matches = new ArrayList<>();
    // 遍历 mappedMethods 数组,匹配异常,添加到 matches 中
    for (Class<? extends Throwable> mappedException : this.mappedMethods.keySet()) {
        if (mappedException.isAssignableFrom(exceptionType)) {
            matches.add(mappedException);
        }
    }
    // 将匹配的结果,排序,选择第一个
    if (!matches.isEmpty()) {
        matches.sort(new ExceptionDepthComparator(exceptionType));
        return this.mappedMethods.get(matches.get(0));
    }
    else {
        return null;
    }
}

逻辑比较简单,关于 org.springframework.core.ExceptionDepthComparator 比较器,胖友自己点击 传送门 查看。大体的逻辑是,比较它们和目标类的继承层级,越小越匹配。

getExceptionHandlerMethod

getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) 方法,获得异常对应的 ServletInvocableHandlerMethod 对象,代码如下:


@Nullable
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(
        @Nullable HandlerMethod handlerMethod, Exception exception) {

    // 处理器的类型
    Class<?> handlerType = null;

    // <1> 首先,如果 handlerMethod 非空,则先获得 Controller 对应的 @ExceptionHandler 处理器对应的方法
    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
        handlerType = handlerMethod.getBeanType();
        // 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象
        ExceptionHandlerMethodResolver resolver = this.exceptionHandlerCache.get(handlerType);
        if (resolver == null) {
            resolver = new ExceptionHandlerMethodResolver(handlerType);
            this.exceptionHandlerCache.put(handlerType, resolver);
        }
        // 获得异常对应的 Method 处理方法
        Method method = resolver.resolveMethod(exception);
        // 如果获得该异常对应的 Method 处理方法,则创建 ServletInvocableHandlerMethod 对象,并返回
        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.
        // 获得 handlerType 的原始类。因为,此处有可能是代理对象
        if (Proxy.isProxyClass(handlerType)) {
            handlerType = AopUtils.getTargetClass(handlerMethod.getBean());
        }
    }

    // <2> 其次,使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法
    for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) {
        ControllerAdviceBean advice = entry.getKey();
        // 如果 ControllerAdvice 支持当前的 handlerType
        if (advice.isApplicableToBeanType(handlerType)) {
            // 获得 handlerType 对应的 ExceptionHandlerMethodResolver 对象
            ExceptionHandlerMethodResolver resolver = entry.getValue();
            // 获得异常对应的 Method 处理方法
            Method method = resolver.resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(advice.resolveBean(), method);
            }
        }
    }

    // 最差,获取不到
    return null;
}
  1. 首先,如果 handlerMethod 非空,则先获得 Controller 对应的 @ExceptionHandler 处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回
  2. 其次,使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回
  3. 最差,获取不到,返回 null

上面第 2 种情况也就是示例中定义的方法哦~

doResolveHandlerMethodException

实现 doResolveHandlerMethodException(ttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) 方法,处理异常,代码如下:


@Override
@Nullable
protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
        HttpServletResponse response, @Nullable HandlerMethod handlerMethod, Exception exception) {
    // <1> 获得异常对应的 ServletInvocableHandlerMethod 对象
    ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
    if (exceptionHandlerMethod == null) {
        return null;
    }

    // <1.1> 设置 ServletInvocableHandlerMethod 对象的相关属性
    if (this.argumentResolvers != null) {
        exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
    }
    if (this.returnValueHandlers != null) {
        exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
    }

    // <1.2> 创建 ServletWebRequest 对象
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    // <1.3> 创建 ModelAndViewContainer 对象
    ModelAndViewContainer mavContainer = new ModelAndViewContainer();

    try {
        if (logger.isDebugEnabled()) {
            logger.debug("Using @ExceptionHandler " + exceptionHandlerMethod);
        }
        // <2> // 执行处理该异常的方法 ServletInvocableHandlerMethod 的调用
        Throwable cause = exception.getCause();
        if (cause != null) {
            // Expose cause as provided argument as well
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
        }
        else {
            // Otherwise, just the given exception as-is
            exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
        }
    }
    catch (Throwable invocationEx) {
        // Any other than the original exception is unintended here,
        // probably an accident (e.g. failed assertion or the like).
        // <2.1> 发生异常,则直接返回
        if (invocationEx != exception && logger.isWarnEnabled()) {
            logger.warn("Failure in @ExceptionHandler " + exceptionHandlerMethod, invocationEx);
        }
        // Continue with default processing of the original exception...
        return null;
    }

    // <3> 如果 mavContainer 已处理,则返回 '空的' ModelAndView 对象。
    if (mavContainer.isRequestHandled()) {
        return new ModelAndView();
    }
    // <4> 如果 mavContainer 未处,则基于 `mavContainer` 生成 ModelAndView 对象
    else {
        ModelMap model = mavContainer.getModel();
        HttpStatus status = mavContainer.getStatus();
        // <4.1> 创建 ModelAndView 对象,并设置相关属性
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, status);
        mav.setViewName(mavContainer.getViewName());
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        // <4.2>
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
        }
        return mav;
    }
}
  1. 调用 getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) 方法,获得异常对应的 ServletInvocableHandlerMethod 对象
  1. 设置 ServletInvocableHandlerMethod 对象的相关属性,参数解析器,返回结果处理器
  2. 创建 ServletWebRequest 对象 webRequest,封装了请求和响应
  3. 创建 ModelAndViewContainer 对象 mavContainer,用于获取 ModelAndView 对象
  1. 执行处理该异常的方法,ServletInvocableHandlerMethod 对象的调用
  1. 发生异常,则直接返回
  1. 如果 mavContainer 已处理,则返回 “空的” ModelAndView 对象。😈 这样,就不会被后续的 ViewResolver 所处理。为什么呢?可以自己回看下 DispatcherServlet 的 processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,很容易明白
  2. 如果 mavContainer 未处理,则基于 mavContainer 生成 ModelAndView 对象
  1. 创建 ModelAndView 对象,并设置相关属性,视图名称
  2. FlashMapManager 相关,暂时忽略

ResponseStatusExceptionResolver

org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,实现 MessageSourceAware 接口,继承 AbstractHandlerExceptionResolver 抽象类,基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类

构造方法


public class ResponseStatusExceptionResolver extends AbstractHandlerExceptionResolver implements MessageSourceAware {
	@Nullable
	private MessageSource messageSource;
}

applyStatusAndReason

applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response) 方法,设置错误响应,方法如下:


protected ModelAndView applyStatusAndReason(int statusCode, @Nullable String reason, HttpServletResponse response)
        throws IOException {

    // 情况一,如果无错误提示,则响应只设置状态码
    if (!StringUtils.hasLength(reason)) {
        response.sendError(statusCode);
    }
    // 情况二,如果有错误信息,则响应设置状态码 + 错误提示
    else {
        // 进一步解析错误提示,如果有 messageSource 的情况下
        String resolvedReason = (this.messageSource != null ?
                this.messageSource.getMessage(reason, null, reason, LocaleContextHolder.getLocale()) :
                reason);
        // 设置
        response.sendError(statusCode, resolvedReason);
    }
    // 创建“空” ModelAndView 对象,并返回
    return new ModelAndView();
}

注意,此处返回的也是“空”的 ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理

doResolveException

实现 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:


@Override
@Nullable
protected ModelAndView doResolveException(HttpServletRequest request, HttpServletResponse response, 
                                          @Nullable Object handler, Exception ex) {
    try {
        // <1> 情况一,如果异常是 ResponseStatusException 类型,进行解析并设置到响应
        if (ex instanceof ResponseStatusException) {
            return resolveResponseStatusException((ResponseStatusException) ex, request, response, handler);
        }

        // <2> 情况二,如果有 @ResponseStatus 注解,进行解析并设置到响应
        ResponseStatus status = AnnotatedElementUtils.findMergedAnnotation(ex.getClass(), ResponseStatus.class);
        if (status != null) {
            return resolveResponseStatus(status, request, response, handler, ex);
        }

        // <3> 情况三,使用异常的 cause 在走一次情况一、情况二的逻辑。
        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;
}
  1. 情况一,如果异常是 ResponseStatusException 类型,进行解析并设置到响应,调用 resolveResponseStatus(ResponseStatusException ex, HttpServletRequest request, HttpServletResponse response, Object handler) 方法,如下:
protected ModelAndView resolveResponseStatusException(ResponseStatusException ex,
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws Exception {
    int statusCode = ex.getStatus().value();
    String reason = ex.getReason();
    return applyStatusAndReason(statusCode, reason, response);
}
  1. 情况二,如果有 @ResponseStatus 注解,进行解析并设置到响应,调用 resolveResponseStatus(ResponseStatus responseStatus, HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,如下:
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);
}
  1. 情况三,使用异常的 cause 再走一次情况一情况二的逻辑

DefaultHandlerExceptionResolver

org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver,继承 AbstractHandlerExceptionResolver 抽象类,默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码

其中,实现 doResolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,代码如下:


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

逻辑不复杂,根据不同的异常,设置响应码和错误信息,例如 HTTP 方法类型不支持,如下:


protected ModelAndView handleHttpRequestMethodNotSupported(HttpRequestMethodNotSupportedException ex,
        HttpServletRequest request, HttpServletResponse response, @Nullable Object handler) throws IOException {

    String[] supportedMethods = ex.getSupportedMethods();
    if (supportedMethods != null) {
        response.setHeader("Allow", StringUtils.arrayToDelimitedString(supportedMethods, ", "));
    }
    // 405 状态码,HTTP Method 不支持
    response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED, ex.getMessage());
    return new ModelAndView();
}

注意,返回的都是“空”的 ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理

总结

本文对 Spring MVC 中的 HandlerExceptionResolver 组件进行分析,处理器异常解析器,将处理器( handler )执行时发生的异常(也就是处理请求,执行方法的过程中发生的异常)解析(转换)成对应的 ModelAndView 结果

HandlerExceptionResolver 的实现类没有特别多,不过也采用了组合模式,如果某个异常处理器进行处理了,也就是返回的 ModeAndView 不为 null(一般都是“空”对象),则直接返回该 ModeAndView 对象

在 Spring MVC 和 Spring Boot 中,默认情况下都有三种 HandlerExceptionResolver 实现类,他们的顺序如下:

  1. ExceptionHandlerExceptionResolver:基于 @ExceptionHandler 配置 HandlerMethod 的 HandlerExceptionResolver 实现类。例如通过 @ControllerAdvice 注解自定义异常处理器,加上@ExceptionHandler注解指定方法所需要处理的异常类型,这种方式就在这个实现类中实现的。没有使用过这两个注解可以参考上面的示例
  2. ResponseStatusExceptionResolver:基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加 @ResponseStatus 注解,指定该方法发生异常时,需要设置的 code 响应码和 reason 错误信息
  3. DefaultHandlerExceptionResolver:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码错误信息

到这里,已经分析了 Spring MVC 的 DispatcherServlet,以及 MultipartResolver、HandlerMapping、HandlerAdapter 和 HandlerExceptionResolver 四个组件,只想说:Spring MVC 的设计者太腻害了~😛