Spring MVC 源码- HandlerExceptionResolver 组件

news2024/11/18 3:27:08

HandlerExceptionResolver 组件

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

回顾

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

@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 接口体系的结构如下:

初始化过程

DispatcherServletinitHandlerExceptionResolvers(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 中,默认开启

  1. 如果“关闭”探测功能,则获得 Bean 名称为 "handlerExceptionResolver" 对应的 Bean ,将其添加至 handlerExceptionResolvers

  1. 如果未获得到,则获得默认配置的 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 实现类

接下来会对 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

  1. org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver

  1. 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

  1. 如果 mappedHandlerClasses 包含该 handler 处理器所在类,则返回 true

  1. 如果 mappedHandlersmappedHandlerClasses 都为空,说明直接匹配

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

如果想要阻止响应缓存,需要设置 preventResponseCachingtrue

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) 方法,阻止缓存

  1. 调用 doResolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) 抽象方法,执行解析异常,返回 ModelAndView 对象

  1. 如果 ModelAndView 对象非空,则打印日志

  1. 返回执行结果

  1. 不可应用,直接返回 null

AbstractHandlerMethodExceptionResolver

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

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

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

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,详情见下文

  1. 初始化 argumentResolvers 属性。其中,#getDefaultArgumentResolvers() 方法,获得默认的 HandlerMethodArgumentResolver 数组,详情见下文

  1. 初始化 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. 调用 ControllerAdviceBeanfindAnnotatedBeans(ApplicationContext context) 方法,扫描 @ControllerAdvice 注解的 Bean 们,并将进行排序,这里就会扫描到上面示例中 CustomizeExceptionHandler 自定义异常处理类

  1. 遍历 ControllerAdviceBean 数组

  1. 创建扫描该 ControllerAdviceBean 对应的类型 ExceptionHandlerMethodResolver 对象 resolver,该对象在下面会分析

  1. @ExceptionHandler 注解,则将resolver添加到 exceptionHandlerAdviceCache

  1. 如果该 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);
            }
        }
    }
}

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

  1. 遍历 @ExceptionHandler 注解的方法

  1. 调用 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()));
}

调用 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;
}

按照 exceptionexception.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 对象并返回

  1. 其次,使用 ControllerAdvice 对应的 @ExceptionHandler 处理器对应的方法,如果获取到了,则将该 Method 封装成 ServletInvocableHandlerMethod 对象并返回

  1. 最差,获取不到,返回 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 对象的相关属性,参数解析器,返回结果处理器

  1. 创建 ServletWebRequest 对象 webRequest,封装了请求和响应

  1. 创建 ModelAndViewContainer 对象 mavContainer,用于获取 ModelAndView 对象

  1. 执行处理该异常的方法,ServletInvocableHandlerMethod 对象的调用

  • 《HandlerAdapter 组件(二)之 ServletInvocableHandlerMethod》中已经详细的分析过了

  • 此处传入了 Object... providedArgs 参数为 exceptionhandlerMethod 变量,这也是为什么 @ExceptionHanlder 注解的方法,可以设置为这两个参数

  1. 发生异常,则直接返回

  1. 如果 mavContainer 已处理,则返回 “空的” ModelAndView 对象。这样,就不会被后续的 ViewResolver 所处理。为什么呢?可以自己回看下 DispatcherServlet 的 processHandlerException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) 方法,很容易明白

  1. 如果 mavContainer 未处理,则基于 mavContainer 生成 ModelAndView 对象

  1. 创建 ModelAndView 对象,并设置相关属性,视图名称

  1. 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 类型,进行解析并设置到响应,调用 resolveResponseStatusException(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);
}

2、情况二,如果有 @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);
}

3、情况三,使用异常的 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注解指定方法所需要处理的异常类型,这种方式就在这个实现类中实现的。没有使用过这两个注解可以参考上面的示例

  1. ResponseStatusExceptionResolver:基于 @ResponseStatus 提供错误响应的 HandlerExceptionResolver 实现类。例如在方法上面添加 @ResponseStatus 注解,指定该方法发生异常时,需要设置的 code 响应码和 reason 错误信息

  1. DefaultHandlerExceptionResolver:默认 HandlerExceptionResolver 实现类,针对各种异常,设置错误响应码。例如 HTTP Method 不支持,则在这个实现类中往响应中设置错误码错误信息

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/370578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Python学习-----模块5.0(文件管理大师-->os模块)

目录 前言&#xff1a; 1.os.getcwd() 2. os.listdir(path) 3.os.walk(path) 4.os.path.exists(path) 5.os.mkdir(path) 6.os.makedirs(path,exist_okTrue) 7.os.rmdir(path) 8.os.remove(path) 9.os.path.join(p1,p2) 10.os.path.split(path) 11.os.path.isdi…

【python】类的详解

注&#xff1a;最后有面试挑战&#xff0c;看看自己掌握了吗 文章目录PO verses OOPOOO当一个类很复杂的时候&#xff0c;考虑多弄一个类的改造私有类的模块化静态类verses动态类动态类查看模块源代码对象机制的基石 PyObjectPO verses OO PO PO耦合性高&#xff0c;很多过程…

手写Android性能监测工具,支持Fps/流量/内存/启动等

App性能如何量化:如何衡量一个APP性能好坏&#xff1f;直观感受就是&#xff1a;启动快、流畅、不闪退、耗电少等感官指标&#xff0c;反应到技术层面包装下就是&#xff1a;FPS&#xff08;帧率&#xff09;、界面渲染速度、Crash率、网络、CPU使用率、电量损耗速度等&#xf…

Linux命令之awk

awk是一个有强大的文本格式化能力的linux命令&#xff0c;早期是在Unix上实现的&#xff0c;linux后来也可以使用了&#xff0c;我们在Linux上使用的awk是gawk&#xff08;GNU awk的意思&#xff09; 语法 awk [option] 模式{动作} file option表示awk的可选参数&#xff0c;可…

mybatis与jpa

1、官方文档 mybatis&#xff1a;mybatis-spring – jpa&#xff1a;https://springdoc.cn/spring-data-jpa/ 应用文档 jpa详解_java菜鸟1的博客-CSDN博客 JPA简介及其使用详解_Tourist-xl的博客-CSDN博客_jpa的作用 2、使用比较 mybatis一般用于互联网性质的项目&#x…

zabbix4.0 Web页面配置 - 聚合图形的实现

目录 1、主机组Host groups配置 创建主机组 ​编辑 将一个主机添加至刚才创建的主机里面 2、用户参数UserParameter设置 示例&#xff1a; 添加一个参数&#xff1a;show.host.messages 模拟zabbix模板里面的参数再添加一个userparameter 3、触发器设置 示例&#xff1a; …

浏览器缓存之强缓存和协商缓存

为什么需要缓存? - 缓存的优点: 1.减少对服务器的访问次数,减轻了服务器的压力 2.节省用户网络带宽(就是省钱,带宽都是按流量算钱的) 3.从缓存读取更匀速减少等待优化了用户体验 - 缓存的缺点 资源被缓存后用户不能及时获取不到最新的资源,所以缓存不能乱用 强缓存 涉…

TypeScript快速上手语法+结合vue3用法

TypeScript快速上手语法结合vue3用法 前言&#xff1a; 本篇内容不涉及TypeScript安装以及配置&#xff0c;具体安装及配置篇可以看下面目录&#xff0c;本篇只涉及TypeScript语法相关内容&#xff0c;及结合vue3的用法。不讲废话&#xff0c;简单直接直接开撸。 目录 Type…

理想汽车--笔试(算法)

笔试分为选择题和编程题&#xff0c;选择题考的很全面&#xff0c;包括概率论、数据库、机器学习、python、数据结构。 选择题 1.在某些规划的分类器中&#xff0c;依据规划质量的某种度量对规划排序&#xff0c;保证每一个测试记录都是由覆盖它的‘最好的’规格来分类&#…

LeetCode-54. 螺旋矩阵

题目来源 54. 螺旋矩阵 题目思路 while循环只遍历"环"&#xff0c;不成环就不遍历了 四个边界 上边界 top : 0下边界 bottom : matrix.length - 1左边界 left : 0右边界 right : matrix[0].length - 1 矩阵不一定是方阵 top < bottom && left < r…

使用git从github.com中clone一个项目的源代码---git与github的安装配置与使用入门

本文目录git简介github简介git的安装github的配置1&#xff0c;注册github帐号2&#xff0c;登录github3&#xff0c;配置git4&#xff0c;生成密钥5&#xff0c;在github中添加密钥6&#xff0c;使用git从github.com中clone一个项目的源代码git简介 Git是一个开源的版本控制管…

Java知识复习(六)常见的设计模式(单例、原型、工厂)

前言 发现无论是什么设计模式&#xff0c;其实总的原则就是减少耦合&#xff0c;增加可复用代码&#xff0c;使系统更易于扩展 参考书籍&#xff1a;《秒懂设计模式》 1、单例模式&#xff08;Singleton&#xff09; 单例模式&#xff1a;即单一的实例&#xff0c;同时提供几…

【java web篇】项目管理构建工具Maven简介以及安装配置

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

【离线数仓-8-数据仓库开发DWD层-交易域相关事实表】

离线数仓-8-数据仓库开发DWD层-交易域相关事实表离线数仓-8-数据仓库开发DWD层-交易域相关事实表一、DWD层设计要点二、交易域相关事实表1.交易域加购事务事实表1.加购事务事实表 前期梳理2.加购事务事实表 DDL表设计分析3.加购事务事实表 加载数据分析1.首日全量加购的数据加载…

基于APRX并行架构的高速QPSK解调实现(Matlab仿真篇)

由于QPSK系统下变频之后的信号中频为720MHz,信息符号速率为500Mbps,因此,采用传统的串行解调方案已无法在FPGA中实现解调。因此,本方案采用基于APRX并行架构实现对高速率的QPSK解调。如图1所示,为并行全数字QPSK接收机实现架构。 图1 并行全数字QPSK接收机实现架构 1 高速…

Golang 接口笔记

基本介绍接口是一个数据类型&#xff0c;可以定义一组方法&#xff0c;但都不需要实现。并且interface中不能包含任何变量。到某个自定义类型要使用的时候&#xff0c;再根据具体情况把这些方法实现出来语法type 接口名 interface {method1(参数列表) 返回值列表method2(参数列…

UG NX二次开发(C#)-CAM-点击插件自动进入CAM模块

文章目录 1、前言2、调用CAM模块错误2、进入加工模块1、前言 UG NX软件中CAM模块作为一个很重要的,也是其特别亮点的功能模块,能实现车、铣、磨、钻等加工工艺编程,但是由于其是通用性比较强,对于专业上的可能不能完全满足要求,这就要求我们在CAM模块下进行二次开发。我们…

操作系统核心知识点整理--进程篇

操作系统核心知识点整理--进程篇什么是系统调用进程篇什么是进程什么是线程从一次fork调用看linux进程和线程的本质区别小结用户级线程和内核级线程的区别进程的状态进程的切换进程调度并发问题死锁参考本文主要面向应用层软件开发人员整理一篇必须了解的操作系统核心知识图谱&…

maya多边形顶点变形批量传递方法

一、问题描述 做项目时&#xff0c;对于重复更改相同模型的顶点位置需要大量重复操作&#xff0c;maya默认提供了多边形属性传递的方法&#xff0c;如下图&#xff1a; 但一次只能执行一次&#xff0c;并且带有大量历史节点&#xff0c;此方式的好处是&#xff0c;可以实现实…

《零成本实现Web自动化测试--基于Selenium》 Selenium-RC

一. 简介 Selenium-RC可以适应更复杂的自动化测试需求&#xff0c;而不仅仅是简单的浏览器操作和线性执行。Selenium-RC能够充分利用编程语言来构建更复杂的自动化测试案例&#xff0c;例如读写文件、查询数据库和E-mail邮寄测试报告。 当测试案例遇到selenium-IDE不支持的逻辑…