20220620 DispatcherServlet 处理请求
概述
DispatcherServlet
的继承关系
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 请求处理流程
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
- 是否允许在
DEBUG
和TRACE
级别记录请求详细信息 - 配合
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
方法:
getHandler
:SimpleUrlHandlerMapping
返回ResourceHttpRequestHandler
getHandlerAdapter
:返回HttpRequestHandlerAdapter
ha.handle
:ResourceHttpRequestHandler
在默认路径中找到对应文件,以流的形式写入response
,方法返回mv
为null
processDispatchResult
:因为mv
为null
,不做任何处理
这里访问 http://localhost:8080
也会得到同样结果,但是过程上有差别,会经过一次转发,然后到 /index.html
- 访问
http://localhost:8080
首先得到的 Handler 是ParameterizableViewController
,view
是forward: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 FileUploadorg.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 个,默认顺序如下:
-
RequestMappingHandlerMapping requestMappingHandlerMapping
EnableWebMvcConfiguration#requestMappingHandlerMapping
- 从
@Controller
类中的类型和方法级别的@RequestMapping
注解创建RequestMappingInfo
实例。
- 从
-
BeanNameUrlHandlerMapping beanNameHandlerMapping
WebMvcConfigurationSupport#beanNameHandlerMapping
- 将名称以
/
开头的 bean 映射到 URL 请求
- 将名称以
-
RouterFunctionMapping routerFunctionMapping
WebMvcConfigurationSupport#routerFunctionMapping
- 支持
RouterFunctions
的HandlerMapping
实现。 - 如果在构建时没有提供
RouterFunction
,这个映射会检测应用上下文中的所有路由函数,并依次查询。
- 支持
-
HandlerMapping resourceHandlerMapping
WebMvcConfigurationSupport#resourceHandlerMapping
- 实际类型是
SimpleUrlHandlerMapping
- 从 URL 映射到请求处理器 bean 的
HandlerMapping
接口的实现。支持映射到 bean 实例和映射到 bean 名称
- 从 URL 映射到请求处理器 bean 的
-
WelcomePageHandlerMapping welcomePageHandlerMapping
EnableWebMvcConfiguration#welcomePageHandlerMapping
- 应用欢迎页面的
AbstractUrlHandlerMapping
。支持静态和模板文件。如果静态和模板索引页面都可用,则首选静态页面。- 静态页面是指
index.html
- 模板是指
index
- 静态页面是指
- 如果在静态文件路径上找到
index.html
,排序变为第 2- 设置排序:
WelcomePageHandlerMapping#setRootViewName
- 设置排序:
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 个,默认顺序如下:
RequestMappingHandlerAdapter requestMappingHandlerAdapter
EnableWebMvcConfiguration#requestMappingHandlerAdapter
- 支持
HandlerMethod
HandlerFunctionAdapter handlerFunctionAdapter
WebMvcConfigurationSupport#handlerFunctionAdapter
- 支持
HandlerFunction
HttpRequestHandlerAdapter httpRequestHandlerAdapter
WebMvcConfigurationSupport#httpRequestHandlerAdapter
- 适配使用普通的
HttpRequestHandler
接口和通用的org.springframework.web.servlet.DispatcherServlet
。支持实现LastModified
接口的处理器。这是一个 SPI 类,应用程序代码不应该直接使用。 - 可以支持
HttpRequestHandler
,实现类有ResourceHttpRequestHandler
,对应SimpleUrlHandlerMapping
SimpleControllerHandlerAdapter simpleControllerHandlerAdapter
WebMvcConfigurationSupport#simpleControllerHandlerAdapter
- 适配使用普通的
Controller
工作流接口和通用的org.springframework.web.servlet.DispatcherServlet
。支持实现LastModified
接口的处理器。这是一个 SPI 类,应用程序代码不直接使用。 - 支持
Controller
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
RequestBodyAdvice
和 ResponseBodyAdvice
默认有一个 JsonViewRequestBodyAdvice
和 JsonViewResponseBodyAdvice
是通过自动配置类中的 @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 ,类型分别是 DefaultErrorAttributes
和 HandlerExceptionResolverComposite
,这两个 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
- 支持
@ResponseStatus
、ResponseStatusException
- 支持
DefaultHandlerExceptionResolver
- 解析标准 Spring MVC 异常并将其转换为相应的 HTTP 状态代码
使用地点
DispatcherServlet#doDispatch
DispatcherServlet#processDispatchResult
DispatcherServlet#processHandlerException
@ResponseStatus
使用应返回的状态代码和原因标记方法或异常类。
当调用处理器方法并覆盖通过其他方式(如 ResponseEntity
或 redirect:
)设置的状态信息时,状态代码将应用于 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
类中包含字段 status
和 reason
,与注解 @ResponseStatus
相同,对这两个的处理也类似
同样由 ResponseStatusExceptionResolver
支持该异常
参见代码:ResponseStatusExceptionResolver#doResolveException