20220620 DispatcherServlet 处理请求

概述

DispatcherServlet 的继承关系

img

DispatcherServlet 初始化

触发初始化时机

处理第一个请求时需要初始化

这里执行的是 servlet.init 方法,也就是 Servlet 初始化生命周期

初始化方法执行链路:

servlet.init
    FrameworkServlet#initServletBean
        FrameworkServlet#initWebApplicationContext
            DispatcherServlet#onRefresh
                DispatcherServlet#initStrategies

DispatcherServlet#initStrategies 对九大组件进行了初始化,日志如下:

2022-06-20 10:30:05 - INFO - [io-8080-exec-1] e.ContainerBase.[Tomcat].[localhost].[/].          log 173 : Initializing Spring DispatcherServlet 'dispatcherServlet'
-2022-06-20 10:30:05 - INFO - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initServletBean 525 : Initializing Servlet 'dispatcherServlet'
-2022-06-20 10:30:05 -TRACE - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initMultipartResolver 517 : Detected org.springframework.web.multipart.support.StandardServletMultipartResolver@689eb9c5
-2022-06-20 10:30:05 -TRACE - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initLocaleResolver 541 : Detected org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver@6dddbda9
-2022-06-20 10:30:05 -TRACE - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initThemeResolver 566 : Detected org.springframework.web.servlet.theme.FixedThemeResolver@206202d5
-2022-06-20 10:30:05 -TRACE - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.nitRequestToViewNameTranslator 716 : Detected DefaultRequestToViewNameTranslator
-2022-06-20 10:30:05 -TRACE - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initFlashMapManager 780 : Detected SessionFlashMapManager
-2022-06-20 10:30:05 -DEBUG - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initServletBean 542 : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data
-2022-06-20 10:30:05 - INFO - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.initServletBean 547 : Completed initialization in 2 ms
-2022-06-20 10:30:05 -TRACE - [io-8080-exec-1] gframework.web.servlet.DispatcherServlet.   traceDebug 116 : GET "/", parameters={}, headers={masked} in DispatcherServlet 'dispatcherServlet'

Spring MVC 请求处理流程

img

Spring Boot 对 DispatcherServlet 的自动配置

org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration.DispatcherServletConfiguration#dispatcherServlet

@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
    DispatcherServlet dispatcherServlet = new DispatcherServlet();
    dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
    dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
    dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
    dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
    dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
    return dispatcherServlet;
}

对应的属性配置有:

  • spring.mvc.dispatch-options-request=true
    • 是否将 OPTIONS 请求分派到 FrameworkServlet doService 方法
    • 不会调用用户定义的处理器方法,而是调用 org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping.HttpOptionsHandler#handle
    • 默认 Allow 标头返回 HEAD, DELETE, POST, GET, OPTIONS, PUT ,共 6 种
  • spring.mvc.dispatch-trace-request=false
    • 是否将 TRACE 请求分派到 FrameworkServlet doService 方法
    • 默认会返回 405 Method Not Allowed
  • spring.mvc.throw-exception-if-no-handler-found=false
    • 如果没有找到处理请求的处理器,是否应该抛出 NoHandlerFoundException
    • 即使设置为 true ,也不会抛出异常,因为会获取到 handler ,是 org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
  • spring.mvc.publish-request-handled-events=true
    • 是否在每个请求结束时发布事件 ServletRequestHandledEvent
  • spring.mvc.log-request-details=false
    • 是否允许在 DEBUGTRACE 级别记录请求详细信息
    • 配合 logging.level.web=trace 使用,效果很好,可以查看到请求和响应的详细信息

DispatcherServlet#doDispatch 方法

几个关键步骤

// HandlerMapping 返回 处理器执行链(Handler 和拦截器列表)
mappedHandler = getHandler(processedRequest);

// 根据 handler 返回 HandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// 执行处理器拦截器 preHandle 方法
mappedHandler.applyPreHandle(processedRequest, response)

// HandlerAdapter 执行 Handler
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

// 执行处理器拦截器 applyPostHandle 方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

// 处理 Handler 结果或异常
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    // 渲染给定的 ModelAndView
    render(mv, request, response);

// 执行处理器拦截器 triggerAfterCompletion 方法
mappedHandler.triggerAfterCompletion(request, response, null);

举例 http://localhost:8080/index.html

增加文件,路径为 src/main/resources/resources/index.html ,这里 index.html 换成 1.txt 也可以正常访问

通过 http://localhost:8080/index.html 即可以正常访问

执行过程对应上面 DispatcherServlet#doDispatch 方法:

  1. getHandlerSimpleUrlHandlerMapping 返回 ResourceHttpRequestHandler
  2. getHandlerAdapter :返回 HttpRequestHandlerAdapter
  3. ha.handleResourceHttpRequestHandler 在默认路径中找到对应文件,以流的形式写入 response ,方法返回 mvnull
  4. processDispatchResult :因为 mvnull ,不做任何处理

这里访问 http://localhost:8080 也会得到同样结果,但是过程上有差别,会经过一次转发,然后到 /index.html

  • 访问 http://localhost:8080 首先得到的 Handler 是 ParameterizableViewControllerviewforward:index.html
  • 第二次过程等同于访问 http://localhost:8080/index.html

九大组件

部分组件是 List

private MultipartResolver multipartResolver;

private LocaleResolver localeResolver;

private ThemeResolver themeResolver;

private List<HandlerMapping> handlerMappings;

private List<HandlerAdapter> handlerAdapters;

private List<HandlerExceptionResolver> handlerExceptionResolvers;

private RequestToViewNameTranslator viewNameTranslator;

private FlashMapManager flashMapManager;

private List<ViewResolver> viewResolvers;

默认初始化策略

Spring Boot 使用的是自动配置中注册的 bean ,不再使用默认初始化策略

DispatcherServlet.properties

# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\
    org.springframework.web.servlet.function.support.RouterFunctionMapping

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
    org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\
    org.springframework.web.servlet.function.support.HandlerFunctionAdapter


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

org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager

代码初始化汇总

名称 是否为 List 查找所有类型相同 bean 标志 查找 bean 名称 使用默认策略
MultipartResolver multipartResolver
LocaleResolver localeResolver
ThemeResolver themeResolver
HandlerMapping detectAllHandlerMappings handlerMapping
HandlerAdapter detectAllHandlerAdapters handlerAdapter
HandlerExceptionResolver detectAllHandlerExceptionResolvers handlerExceptionResolver
RequestToViewNameTranslator viewNameTranslator
ViewResolver detectAllViewResolvers viewResolver
FlashMapManager flashMapManager
  • 组件是 List 的,默认查找所有类型 Bean ,如果 detectXXX 标志为 false ,则根据 bean 名称查找单个 bean
  • 所有组件,如果在上一步查找过程中,查找到了 bean ,则不再使用默认策略
  • Spring Boot 会在查找过程中查找到自动配置注册的 bean ,不会使用默认策略

MultipartResolver

符合 RFC 1867 的多部分文件上传解析策略接口。Spring 中包含两个具体的实现:

  • org.springframework.web.multipart.commons.CommonsMultipartResolver for Apache Commons FileUpload
  • org.springframework.web.multipart.support.StandardServletMultipartResolver for the Servlet 3.0+ Part API

接口定义

public interface MultipartResolver {

    boolean isMultipart(HttpServletRequest request);

    MultipartHttpServletRequest resolveMultipart(HttpServletRequest request) throws MultipartException;

    void cleanupMultipart(MultipartHttpServletRequest request);

}

使用地点

DispatcherServlet#doDispatch
    processedRequest = checkMultipart(request);
        DispatcherServlet#checkMultipart
            this.multipartResolver.resolveMultipart(request);

HandlerMapping

定义请求和处理器对象之间的映射

接口定义

public interface HandlerMapping {

    default boolean usesPathPatterns() {
        return false;
    }

    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

使用地点

DispatcherServlet#doDispatch
    mappedHandler = getHandler(processedRequest);
        DispatcherServlet#getHandler
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

getHandler 遍历所有 HandlerMapping ,如果 mapping.getHandler 返回 null ,继续下一个

Spring Boot 默认注册情况

Spring Boot 默认注册 5 个,默认顺序如下:

  1. RequestMappingHandlerMapping requestMappingHandlerMapping

    • EnableWebMvcConfiguration#requestMappingHandlerMapping
      • @Controller 类中的类型和方法级别的 @RequestMapping 注解创建 RequestMappingInfo 实例。
  2. BeanNameUrlHandlerMapping beanNameHandlerMapping

    • WebMvcConfigurationSupport#beanNameHandlerMapping
      • 将名称以 / 开头的 bean 映射到 URL 请求
  3. RouterFunctionMapping routerFunctionMapping

    • WebMvcConfigurationSupport#routerFunctionMapping
      • 支持 RouterFunctionsHandlerMapping 实现。
      • 如果在构建时没有提供 RouterFunction ,这个映射会检测应用上下文中的所有路由函数,并依次查询。
  4. HandlerMapping resourceHandlerMapping

    • WebMvcConfigurationSupport#resourceHandlerMapping
    • 实际类型是 SimpleUrlHandlerMapping
      • 从 URL 映射到请求处理器 bean 的 HandlerMapping 接口的实现。支持映射到 bean 实例和映射到 bean 名称
  5. WelcomePageHandlerMapping welcomePageHandlerMapping

    • EnableWebMvcConfiguration#welcomePageHandlerMapping
    • 应用欢迎页面的 AbstractUrlHandlerMapping 。支持静态和模板文件。如果静态和模板索引页面都可用,则首选静态页面。
      • 静态页面是指 index.html
      • 模板是指 index
    • 如果在静态文件路径上找到 index.html ,排序变为第 2
      • 设置排序: WelcomePageHandlerMapping#setRootViewName

img

Spring Boot 中所有 HandlerMapping 实现继承了 AbstractHandlerMapping

  • 其中定义了字段 List<Object> interceptors 拦截器

RequestMappingHandlerMapping

-2022-06-09 19:47:41 -TRACE - [          main] .annotation.RequestMappingHandlerMapping.detectHandlerMethods 292 : 
    o.s.b.a.w.s.e.BasicErrorController:
    { [/error]}: error(HttpServletRequest)
    { [/error], produces [text/html]}: errorHtml(HttpServletRequest,HttpServletResponse)
  • 包含字段 ContentNegotiationManager contentNegotiationManager ,通过 @Bean 方法 set 进去
ErrorController

错误处理 Controller

自动配置:ErrorMvcAutoConfiguration#basicErrorController

@Bean
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
                                                 ObjectProvider<ErrorViewResolver> errorViewResolvers) {
    return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
                                    errorViewResolvers.orderedStream().collect(Collectors.toList()));
}
RequestMappingHandlerMapping 的扫描过程

扫描触发时机是创建 bean 时的初始化方法 afterPropertiesSet 被调用的生命周期

RequestMappingHandlerMapping#afterPropertiesSet
    AbstractHandlerMethodMapping#afterPropertiesSet
        AbstractHandlerMethodMapping#initHandlerMethods
            AbstractHandlerMethodMapping#processCandidateBean
                AbstractHandlerMethodMapping#detectHandlerMethods
HandlerMethod

RequestMappingHandlerMapping 会扫描容器中的 bean ,判断是否为 Handler

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#isHandler
protected boolean isHandler(Class<?> beanType) {
    return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
            AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}

从代码中可知,只要类上注解了 RequestMapping 就可以被判定为 Handler ,但是如果类上只有 RequestMapping 注解时,该类不会被扫描为容器 bean ,所以这里还包含一个隐式条件,即应用上下文中需要有该类的实例 。可以被扫描,或者定义 @Bean 方法,或以其他方式注册到 Spring 容器中

然后遍历 Handler 类上的方法,判定是否包含 RequestMapping 注解,然后将方法包装为 HandlerMethod

org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#detectHandlerMethods

判定方法是否包含 RequestMapping 注解的方法:

// org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#createRequestMappingInfo(java.lang.reflect.AnnotatedElement)
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
    RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
    RequestCondition<?> condition = (element instanceof Class ?
                                     getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element));
    return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null);
}

BeanNameUrlHandlerMapping

-2022-06-10 11:17:49 -DEBUG - [          main] ervlet.handler.BeanNameUrlHandlerMapping.detectHandlers  89 : Detected 0 mappings in 'beanNameHandlerMapping'

RouterFunctionMapping

-2022-06-10 10:48:02 -TRACE - [          main] t.function.support.RouterFunctionMapping.logRouterFunctions 182 : 0 RouterFunction(s) in 'routerFunctionMapping'

SimpleUrlHandlerMapping

-2022-06-10 10:48:02 -TRACE - [          main] .servlet.handler.SimpleUrlHandlerMapping.registerHandler 451 : Mapped [/webjars/**] onto ResourceHttpRequestHandler [classpath [META-INF/resources/webjars/]]
-2022-06-10 10:48:02 -TRACE - [          main] .servlet.handler.SimpleUrlHandlerMapping.registerHandler 451 : Mapped [/**] onto ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
-2022-06-10 10:48:02 -DEBUG - [          main] .servlet.handler.SimpleUrlHandlerMapping.  logMappings 188 : Patterns [/webjars/**, /**] in 'resourceHandlerMapping'

这里注册进去的路径的时机是

  • @Bean 方法 WebMvcConfigurationSupport#resourceHandlerMapping
    • WebMvcAutoConfiguration.WebMvcAutoConfigurationAdapter#addResourceHandlers

这里定义的映射是:

URL ResourceHttpRequestHandler 资源路径
/webjars/** [classpath [META-INF/resources/webjars/]
/** [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]

因为 URL 包含 /** ,一般是作为最后一个后备的

HandlerAdapter

javadoc:

MVC 框架 SPI,允许核心 MVC 工作流的参数化。

必须为每个处理器类型实现以处理请求的接口。此接口用于允许 DispatcherServlet 无限扩展。 DispatcherServlet 通过此接口访问所有已安装的处理器,这意味着它不包含特定于任何处理器类型的代码。请注意,处理器可以是 Object 类型。这是为了使来自其他框架的处理器无需自定义编码即可与该框架集成,并允许注解驱动的处理器对象不遵守任何特定的 Java 接口。

此接口不适用于应用程序开发人员。它适用于想要开发自己的 Web 工作流的处理器。

注意: HandlerAdapter 实现者可以实现 org.springframework.core.Ordered 接口,以便能够指定排序顺序(因此是优先级),以便被 DispatcherServlet 应用。无序实例被视为最低优先级。

接口定义

public interface HandlerAdapter {

    boolean supports(Object handler);

    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

    @Deprecated
    long getLastModified(HttpServletRequest request, Object handler);

}

Spring Boot 默认注册情况

Spring Boot 默认注册 4 个,默认顺序如下:

  1. RequestMappingHandlerAdapter requestMappingHandlerAdapter
    • EnableWebMvcConfiguration#requestMappingHandlerAdapter
    • 支持 HandlerMethod
  2. HandlerFunctionAdapter handlerFunctionAdapter
    • WebMvcConfigurationSupport#handlerFunctionAdapter
    • 支持 HandlerFunction
  3. HttpRequestHandlerAdapter httpRequestHandlerAdapter
    • WebMvcConfigurationSupport#httpRequestHandlerAdapter
    • 适配使用普通的 HttpRequestHandler 接口和通用的 org.springframework.web.servlet.DispatcherServlet 。支持实现 LastModified 接口的处理器。这是一个 SPI 类,应用程序代码不应该直接使用。
    • 可以支持 HttpRequestHandler ,实现类有 ResourceHttpRequestHandler ,对应 SimpleUrlHandlerMapping
  4. SimpleControllerHandlerAdapter simpleControllerHandlerAdapter
    • WebMvcConfigurationSupport#simpleControllerHandlerAdapter
    • 适配使用普通的 Controller 工作流接口和通用的 org.springframework.web.servlet.DispatcherServlet 。支持实现 LastModified 接口的处理器。这是一个 SPI 类,应用程序代码不直接使用。
    • 支持 Controller

image-20220624144600724

RequestMappingHandlerAdapter

2022-06-09 19:14:14.788 DEBUG 13144 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : ControllerAdvice beans: 0 @ModelAttribute, 0 @InitBinder, 1 RequestBodyAdvice, 1 ResponseBodyAdvice
RequestBodyAdviceResponseBodyAdvice

默认有一个 JsonViewRequestBodyAdviceJsonViewResponseBodyAdvice

是通过自动配置类中的 @Bean 方法添加进来的:

WebMvcConfigurationSupport#requestMappingHandlerAdapter

if (jackson2Present) {
    adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
    adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
@ModelAttribute
@InitBinder

HandlerExceptionResolver

参考资料

接口定义

public interface HandlerExceptionResolver {

    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);

}

Spring Boot 默认注册情况

Spring Boot 默认配置了两个 HandlerExceptionResolver bean ,类型分别是 DefaultErrorAttributesHandlerExceptionResolverComposite ,这两个 bean 被装配到 DispatcherServlet#handlerExceptionResolvers 中, DefaultErrorAttributes 在前

// ErrorMvcAutoConfiguration#errorAttributes
@Bean
@ConditionalOnMissingBean(value = ErrorAttributes.class, search = SearchStrategy.CURRENT)
public DefaultErrorAttributes errorAttributes() {
    return new DefaultErrorAttributes();
}
// org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#handlerExceptionResolver
@Bean
public HandlerExceptionResolver handlerExceptionResolver(
    @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
    List<HandlerExceptionResolver> exceptionResolvers = new ArrayList<>();
    configureHandlerExceptionResolvers(exceptionResolvers);
    if (exceptionResolvers.isEmpty()) {
        addDefaultHandlerExceptionResolvers(exceptionResolvers, contentNegotiationManager);
    }
    extendHandlerExceptionResolvers(exceptionResolvers);
    HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
    composite.setOrder(0);
    composite.setExceptionResolvers(exceptionResolvers);
    return composite;
}

钩子方法:

  • configureHandlerExceptionResolvers
  • extendHandlerExceptionResolvers

默认唯一 WebConfigurer 实现 WebMvcAutoConfigurationAdapter 中的钩子方法实现为空,因此会调用 addDefaultHandlerExceptionResolvers ,默认添加 3 个 HandlerExceptionResolver ,然后被组合到 HandlerExceptionResolverComposite

  • ExceptionHandlerExceptionResolver
    • 支持 @ExceptionHandler
  • ResponseStatusExceptionResolver
    • 支持 @ResponseStatusResponseStatusException
  • DefaultHandlerExceptionResolver
    • 解析标准 Spring MVC 异常并将其转换为相应的 HTTP 状态代码

使用地点

DispatcherServlet#doDispatch
    DispatcherServlet#processDispatchResult
        DispatcherServlet#processHandlerException

@ResponseStatus

使用应返回的状态代码和原因标记方法或异常类。

当调用处理器方法并覆盖通过其他方式(如 ResponseEntityredirect: )设置的状态信息时,状态代码将应用于 HTTP 响应。

警告:在异常类上使用此注解,或设置此注解的原因属性时,将使用 HttpServletResponse.sendError 方法。

使用 HttpServletResponse.sendError ,响应被认为是完整的,不应再写入任何内容。此外,Servlet 容器通常会编写一个 HTML 错误页面,因此会使用不适合 REST API 的原因。对于这种情况,最好使用 org.springframework.http.ResponseEntity 作为返回类型,并完全避免使用 @ResponseStatus

请注意,控制器类也可以使用 @ResponseStatus 注解,然后由该类及其子类中的所有 @RequestMapping@ExceptionHandler 方法继承,除非被方法上的本地 @ResponseStatus 声明覆盖。

使用示例

定义异常类时使用此注解,当控制器抛出异常时,返回状态码和错误信息

@ResponseStatus(value = HttpStatus.FORBIDDEN,reason = "用户名和密码不匹配!")
public class UserNameNotMatchPasswordException extends RuntimeException{

}

RequestMappingHandlerMapping 的扫描过程中( AbstractHandlerMethodMapping#detectHandlerMethods ),被封装在 HandlerMethod

ResponseStatusExceptionResolver 支持该注解

参见代码:ResponseStatusExceptionResolver#doResolveException

设置响应的状态信息:

mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
    RequestMappingHandlerAdapter#invokeHandlerMethod
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
            ServletInvocableHandlerMethod#invokeAndHandle
                setResponseStatus(webRequest);

存在代码:

String reason = getResponseStatusReason();
if (StringUtils.hasText(reason)) {
    response.sendError(status.value(), reason);
}
else {
    response.setStatus(status.value());
}

因此使用注解时,有没有 reason 存在很大区别,如果存在 reason ,会再进行一次转发,到 /error ;如果不存在,就只会设置状态码,不会转发到 /error

ResponseStatusException

ResponseStatusException 类中包含字段 statusreason ,与注解 @ResponseStatus 相同,对这两个的处理也类似

同样由 ResponseStatusExceptionResolver 支持该异常

参见代码:ResponseStatusExceptionResolver#doResolveException

posted @ 2022-11-28 09:46  流星<。)#)))≦  阅读(122)  评论(0编辑  收藏  举报