本文重点分析 Spring 整合 Servlet 规范。
- DispatcherServlet:所有请求的入口。
- HandlerMapping:处理器映射器。把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器对象、多个 HandlerInterceptor 拦截器)对象。
HandlerAdapter:处理器适配器。支持多种类型的处理器,包括参数解析(HandlerMethodArgumentResolver)、结果处理(HandlerMethodReturnValueHandler)、媒体类型转换(HttpMessageConverter )。
1. Servlet 规范整合
1.1 Servlet 三大组件
在分析 Spring 整合 Servlet 规范前,我们先了解一下 Servlet 2.0 三大组件:
Servlet:处理客户端请求。Servlet 生命周期, init(ServletConfig) 初始化,destroy() 销毁,service(ServletRequest, ServletResponse) 处理请求。
- ServletContextListener:javaee 容器启动时调用 contextInitialized 方法,销毁时调用 contextDestroyed 方法。
- Filter:拦截器。
总结:javaee 容器启动时,先调用 ServletContextListener#contextInitialized 通知监听者。同时,当收到请求时调用 Servlet#init(ServletConfig) 初始化 Servlet,然后处理请求。
1.2 Spring 整合
Spring 整合 Servlet 规范时,往往会创建父子两个容器。
- ContextLoaderListener:(了解)实现了 ServletContextListener 接口。负责创建父容器,只扫描除了 @Controller 之外的其它 @Componet 派生注解。
- DispatcherServlet:(最核心类)实现了 Servlet 接口。创建子容器时,只扫描 @Controller 注解。DispatcherServlet 是所有请求的入口,进行全局的流程控制。
说明:父子容器也不是必需的,我们完全可以不配置 ContextLoaderListener,此时不会创建父容器。另外,在 Spring Boot 中就只会创建一个容器,参考《Spring-MVC 和 Spring-Boot MVC 父子容器》。
通常,SpringMVC 的 web.xml 配置如下:
<!-- 配置父容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- 配置子容器 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-context-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
2. SpringMVC 架构
Spring 容器启动后,由前端控制器 DispatcherServlet 接收并分发请求,涉及到的核心组件如下:
图1:Spring 核心架构图
说明:DispatcherServlet 处理请求的具体流程如下:
- DispatcherServlet:前端控制器收到请求后自己不进行处理,而是委托给其他的解析器进行处理,作为统一访问点,进行全局的流程控制。
- HandlerMapping:处理器映射器将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器对象、多个 HandlerInterceptor 拦截器)对象。
- HandlerAdapter:处理器适配器将会把处理器包装为适配器,从而支持多种类型的处理器,即适配器设计模式的应用,从而很容易支持很多类型的处理器。
- Handler:处理器相应功能处理方法,并返回一个 ModelAndView 对象(包含模型数据、逻辑视图名)。
- ViewResolver:视图解析器将把逻辑视图名解析为具体的 View。ModelAndView 对象包括 Model (业务对象返回的模型数据)和 View (逻辑视图)两部分。
- View:视图渲染。View 会根据传进来的 Model 模型数据进行渲染,此处的 Model 实际是一个 Map 数据结构。
注:现在 javaweb 都流行前后端分离,基本上都是直接返回 JSON 数据格式,视图解析层 ViewResolver 基本上都快被淘汰了。所以,本文之后会重点分析 @ResponseBody 注解是如何工作的,取代第 5 ~ 6 步。
2.1 DispatcherServlet
2.1.1 初始化
DispatcherServlet 组件初始化 ApplicationContext 时,会通过 onRefresh 调用 initStrategies 方法初始化这九大组件,其中最重要的组件是 HandlerMapping 和 HandlerAdapter。
- MultipartResolver:文件上传。
- LocaleResolver:国际化。
- ThemeResolver:主题。
- HandlerMapping:( 核心组件)处理器映射器将会把请求映射为 HandlerExecutionChain 对象(包含一个 Handler 处理器对象、多个 HandlerInterceptor 拦截器)对象。
- HandlerAdapter:( 核心组件)处理器适配器将处理器包装为适配器。
- HandlerExceptionResolver:异常处理器。
- RequestToViewNameTranslator:如果解析后的 ModleAndView 中没有视图名称,就需要从 Request 中获取到 viewName,这就是 RequestToViewNameTranslator 的功能。
- FlashMapManager:在 redirect 中传递参数。FlashMapManager 就是用来管理 FlashMap。
- ViewResolver:视图处理器。根据 ModleAndView 中视图名称解析视图的资源路径,最终由 View 渲染页面。
说明:DispatcherServlet 初始化这九大组件的过程都差不多,initXXX 方法都是先默认从 ApplicationContext 容器中加载,如果容器中没有,则从 DispatcherServlet.properties 中加载默认组件。下面,我们再看一下,HandlerMapping 和 HandlerAdapter 的默认组件,之后详细分析这些组件的源码。 ``` org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMappingprotected void initStrategies(ApplicationContext context) {
initMultipartResolver(context); // MultipartResolver
initLocaleResolver(context); // LocaleResolver
initThemeResolver(context); // ThemeResolver
initHandlerMappings(context); // List<HandlerMapping>
initHandlerAdapters(context); // List<HandlerAdapter>
initHandlerExceptionResolvers(context); // List<HandlerExceptionResolver>
initRequestToViewNameTranslator(context); // RequestToViewNameTranslator
initViewResolvers(context); // FlashMapManager
initFlashMapManager(context); // List<ViewResolver>
}
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
<a name="YcFl0"></a>
### 2.1.2 请求处理
DispatcherServlet 将所有请求接入后,最终由 DispatcherServlet#doDispatch 分派到不同的 Handle 上处理。
```java
Servlet#service(ServletRequest, ServletResponse)
-> FrameworkServlet#doGet(HttpServletRequest, HttpServletResponse)
-> FrameworkServlet#processRequest(HttpServletRequest, HttpServletResponse)
-> DispatcherServlet#doService(HttpServletRequest, HttpServletResponse)
-> DispatcherServlet#doDispatch(HttpServletRequest, HttpServletResponse)
说明:DispatcherServlet
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 1. 处理异常请求。当返回结果是WebAsyncTask会异常处理
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 2. 处理文件上传。
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 3. (核心逻辑)从HandlerMapping查找HandlerExecutionChain
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 4. (核心逻辑)从HandlerAdapter匹配适配器
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 5. 生命周期的回调。HandlerInterceptor#preHandle
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 6. (核心逻辑)执行Handler。如果restfull,则返回值mv=null
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
// 7. 默认视图名称解析。RequestToViewNameTranslator从request中解析默认的视图名称
applyDefaultViewName(processedRequest, mv);
// 8. 生命周期的回调。HandlerInterceptor#postHandle
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception ex) {
dispatchException = ex;
} catch (Throwable err) {
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
// 9. 结果处理。如视图渲染(view.render)和生命周期回调(HandlerInterceptor#afterCompletion)
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
} catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
} catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
...
}
说明:DispatcherServlet#doDispatch 最核心的逻辑,一是根据 Request 从 HandlerMapping 中匹配 Handler;二是将 Handler 适配 HandlerAdapter;三是执行 HandlerAdapter 并返回 ModelAndView。如果是 restfull 风格,则返回的 ModelAndView 为 null,不用视图渲染。整个请求的时序图如下:
2.1.3 异常处理
第 9 步,如果有异常 dispatchException,则调用 HandlerExceptionResolver 处理该异常。否则,直接按正常逻辑处理,如视图渲染等。
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
// 1. 异常处理:HandlerExceptionResolvers处理异常
boolean errorView = false;
if (exception != null) {
if (exception instanceof ModelAndViewDefiningException) {
mv = ((ModelAndViewDefiningException) exception).getModelAndView();
} else {
Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
mv = processHandlerException(request, response, handler, exception);
errorView = (mv != null);
}
}
// 2. 视图渲染:View#render
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
if (errorView) {
WebUtils.clearErrorRequestAttributes(request);
}
}
...
}
说明:同样,ModelAndView 视图渲染不是我们重点关心的部分。如果是 REST 请求,mv 返回 null。processHandlerException(request, response, handler, exception)
则直接调用 HandlerExceptionResolver 处理异常。Spring 默认的异常处理器有以下三个:
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
- ExceptionHandlerExceptionResolver:处理 @ExceptionHandler。
- ResponseStatusExceptionResolver:处理 ResponseStatusException 或 @ResponseStatus。
- DefaultHandlerExceptionResolver:其它常见异常,如 HttpMediaTypeNotAcceptableException、MissingPathVariableException 等异常。
2.2 HandlerMapping
Spring 默认提供了两种实现方式:
- BeanNameUrlHandlerMapping:将 beanName 注册为 url。请求时根据 request.url 配置 beanName。
- RequestMappingHandlerMapping:(主流方式)@RequestMapping。
BeanNameUrlHandlerMapping 比较简单,我们看一下它的工作原理。 说明:BeanNameUrlHandlerMapping 初始化时,回调 setApplicationContext 方法会解析 url 和 handler 的映射关系保存到 handlerMap 中。这个 Map 的 key 为 beanName,也是 url,而 value 则为 handler。detectHandlers 方法会将 Spring 容器中 beanName 以 ‘/‘ 开头的 bean 保存到 handlerMap 中。处理请求时,会根据 request.url 从这个 handlerMap 中匹配 handler 执行。
2.3 HandlerAdapter
Spring 默认提供了三种实现方式:
- HttpRequestHandlerAdapter:支持 HttpRequestHandler 接口,无返回值。HttpRequestHandler 将参数解析、视图渲染、restfull 类型转换等都交给用户自己完成。
- SimpleControllerHandlerAdapter:支持 Controller 接口,返回 ModelAndView。Controller 将参数解析交给用户自己完成。如果需要返回 restfull 风格,也需要用户自己进行类型转换,此时返回的 mv=null。如果是视图渲染,则由 Spring 容器完成。
- RequestMappingHandlerAdapter:(主流方式)支持 @RequestMapping。参数解析、视图渲染、restfull 类型转换等全部交给 Spring 容器处理。
说明:前两种适配器都是直接调用接口的方法,区别是 HttpRequestHandler 无返回值,而 Controller 接口有返回值。这两种适配器,将参数解析、结果返回都交给用户自己完成。
下面我们看一下 HttpRequestHandler 或 Controller 的示例代码:
@Component("/TestController")
public class TestController implements Controller {
@Nullable
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response)
throws Exception {
response.getOutputStream().write(new String("binarylei").getBytes());
return null;
}
}
@Component("/TestHttpRequestHandler")
public class TestHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.getOutputStream().write(new String("binarylei2").getBytes());
}
}
说明:直接访问 http://localhost:8080/TestController。
3. 总结时刻
推荐阅读
- 《Spring-MVC @InitBinder 和 @ModelAttribute 使用场景》
- 《Spring-MVC @ControllerAdvice 三种使用场景》
- 《Spring-MVC @RequestMapping 使用场景》
- 《Servlet 3.0 新特性详解》IBM:Servlet 3.0 新功能。
- 《Spring-MVC 和 Spring-Boot MVC 父子容器》:Spring MVC 是两个容器,而 Spring Boot MVC 则是一个容器。
- 《SpringMVC框架理解》
每天用心记录一点点。内容也许不重要,但习惯很重要!