「这是我参与2022首次更文挑战的第3天,活动详情查看:2022首次更文挑战」
01-DispatchServlet
DispatchServlet是Spring MVC的核心,其遵循前端控制器设计模式。它是Spring实现的一个servlet,在servlet容器中,负责将请求路由到其他的组件当中。
Java ™ Servlet Specification Version 3.0中对servlet和servlet容器的定义如下:
A servlet is a Java™ technology-based Web component, managed by a container, that generates dynamic content.
The servlet container is a part of a Web server or application server that provides the network services over which requests and responses are sent, decodes MIME-based requests, and formats MIME-based responses. A servlet container also contains and manages servlets through their lifecycle.
01.1-DispatcherServlet处理请求的基本流程
[1] 描述了一个请求到达DispatchServlet后的基本流程:
- 请求离开浏览器到达DispatchServlet
- DispatchServlet负责根据请求携带的URL信息,确定请求被路由到哪里
- DispatchServlet将请求转交给2.中选中的控制器
- 控制器返回模型和视图名给DispatchServlet
- DispatchServlet根据视图名确定具体的视图实现
- 具体的实现将4.中的逻辑视图渲染成真正的视图
- 视图被返回给浏览器
了解DispatchServlet处理请求的基本流程后,不纠结具体的实现细节,让我们先来看一下在Spring MVC中如何配置DispatchServlet?
01.2-Spring MVC中配置DispatcherServlet
-
通过实现
WebApplicationInitializer接口的方式public class MyWebApplicationInitializer implements WebApplicationInitializer { @Override public void onStartup(javax.servlet.ServletContext servletContext) throws ServletException { // Load Spring web application configuration AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.register(AppConfig.class); // Create and register the DispatcherServlet DispatcherServlet servlet = new DispatcherServlet(context); ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet); registration.setLoadOnStartup(1); registration.addMapping("/app/*"); } }Servlet容器在启动时,会通过SPI机制,回调到
onStartup方法,具体参考 Spring MVC 「2」WebApplicationInitializer的工作原理。回到上面的代码,在
onStartup方法中,我们创建了一个DispatcherServlet,并将其注册在servletContext中,并将/app/*与其绑定。 -
通过继承
AbstractAnnotationConfigDispatcherServletInitializer类的方式public class MyDispatchServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[0]; } /** * 仅在需要应用程序上下文需要层次结构时需要 * 如果应用仅需要一个上下文,则可以返回 null, * 而由getRootConfigClasses返回应用上下文 */ @Override protected Class<?>[] getServletConfigClasses() { return new Class[0]; } @Override protected String[] getServletMappings() { return new String[0]; } }通过对
AbstractAnnotationConfigDispatcherServletInitializer继承层次分析发现,其实现了WebApplicationInitializer接口,最终也归结为方式的另类形式,只不过省去了手动实例化DispatcherServlet的麻烦。与方式1相比,更推荐方式2,因为使用起来更方便。
-
通过实现
WebMvcConfigurer接口的方式方式3与前面两种方式不太一样,具体不同之处逐个进行分析。
首先,实现
WebMvcConfigurer接口并不能实例化DispatcherServlet对象。其次,
WebMvcConfigurer仅仅是为了让用户可以修改DispatcherServlet在处理请求时使用的各种对象,例如拦截器(addInterceptors())、视图解析器(configureViewResolvers())等等最后,使用Spring MVC时,通常会在配置文件上添加
@EnableWebMvc注解。该注解会通过@Import引入另一个配置文件org.springframework.web.servlet.config.annotation.DelegatingWebMvcConfiguration,DelegatingWebMvcConfiguration中定义了setConfigurers方法:/** * 自动从容器中请求所有实现了 WebMvcConfigurer 接口的Bean * 所以,如果我们自己实现了 WebMvcConfigurer 接口, * 实现类会在最终添加到 configurers 中 */ @Autowired(required = false) public void setConfigurers(List<WebMvcConfigurer> configurers) { if (!CollectionUtils.isEmpty(configurers)) { this.configurers.addWebMvcConfigurers(configurers); } }
[1] Spring in Action [2] Specifications
02-结合源码分析DispatcherServlet处理请求的具体流程
02.1-DispatcherServlet初始化过程
当Servlet容器启动时,会通过SPI机制回调到org.springframework.web.SpringServletContainerInitializer#onStartup方法。在该方法中会遍历调用类路径下所有实现了WebApplicationInitializer接口的类的onStartup方法,最终程序流会进入我们自己的初始化类中。
以01.2节中介绍的第2中方式为例(即通过继承AbstractAnnotationConfigDispatcherServletInitializer来初始化DispatcherServlet) ,程序流会进入到我们的实现类中MyDispatchServletInitializer。
// 该方法继承自抽象类 AbstractDispatcherServletInitializer
public void onStartup(ServletContext servletContext) throws ServletException {
/**
* 实际上会调用继承自父类 AbstractContextLoaderInitializer 的
* registerContextLoaderListener方法,
* 主要用来创建 rootApplicationContext 和创建监听器
*/
super.onStartup(servletContext);
/**
* 主要用来创建 servletContext 及 DispatcherServlet对象
* 将 DispatcherServlet 对象绑定到某个路径上(由getServletMappings()指定)
*/
this.registerDispatcherServlet(servletContext);
}
02.2-一个请求的处理流程
DispatcherServlet是Servlet接口的一个实现,其接口及类实现关系如下图所示:
当请求离开浏览器进入到tomcat以后,最终会被Servlet容器传递给DispatcherServlet继承来的方法
HttpServlet#service中。在service方法中,根据请求中的HTTP方法,会交由特定的方法去处理:
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
后续代码以doGet为例,请求被路由到继承来的FrameworkServlet#processRequest方法中,然后到达DispatcherServlet#doService方法中,在设置过一系列属性后(例如),请求最终到达DispatcherServlet#doDispatch方法中。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
try {
ModelAndView mv = null;
Object dispatchException = null;
try {
processedRequest = this.checkMultipart(request);
multipartRequestParsed = processedRequest != request;
// 1. 从handlerMapping中查找处理器(遇到的第一个合适的)
mappedHandler = this.getHandler(processedRequest);
if (mappedHandler == null) {
this.noHandlerFound(processedRequest, response);
return;
}
// 2. 从适配器中找到第一个匹配的适配器
HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
return;
}
}
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 3. 处理请求
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
this.applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
} catch (Exception var20) {
dispatchException = var20;
} catch (Throwable var21) {
dispatchException = new NestedServletException("Handler dispatch failed", var21);
}
this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
} catch (Exception var22) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
} catch (Throwable var23) {
this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
}
} finally {
if (asyncManager.isConcurrentHandlingStarted()) {
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
} else if (multipartRequestParsed) {
this.cleanupMultipart(processedRequest);
}
}
}
至此,请求的处理过程基本完毕。当然,我们依然忽略了许多的处理细节,这些将在后面的文章中详细分析。