概述

提到spring mvc,很多初学者都会和spring搞混,前几篇文章分析了和web相关的tomcat、spring等源码, 现在来看一下spring mvc吧。

原理解剖

正如Spring 起始于xml,我们的web应用起始于web.xml。而web.xml中最重要的配置就是listener和servlet了, 在分析tomcat源码的时候,我们已经看到了listener的作用,接下来就来看一下servlet吧。

spring mvc框架是依托于spring容器的。在加载完listener之后,容器将会加载load-on-startup为非负数的servlet, 通常情况下就是我们的dispatcherservlet了,dispatcherservlet在初始化的过程中会建立一个自己的IOC容器上下文: servletWebApplicationContext,并会以contextLoaderListener建立的Root上下文作为自己的父级上下文(这里 上下文可以理解为容器)。dispatcherservlet持有的上下文默认是xmlwebapplicationContext,servlet有自己的bean空间, 同时还可以访问到父容器空间。

源码解析

DispatcherServlet

上文我们了解到web应用启动的过程中会创建的容器及流程,接下来我们来看一下最核心的类dispatcherservlet。 dispatcherservlet是spring mvc中最为核心的类,起到前置控制器的作用,负责请求的分发。其类的继承关系图如下: 上图可以发现dispatcherservlet本质上也是一个servlet,既然是servlet,那么肯定也是有固定的生命周期。

  • 初始化
  • 处理请求
  • 销毁

我们分别来看一下这些阶段都做了那些事情吧

初始化

初始化方法存在于HttpServletBean中,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public final void init() throws ServletException {

// Set bean properties from init parameters.
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
.....
}

// Let subclasses do whatever initialization they like.
initServletBean();
}

上面的过程是读取init-param中的参数设置到dispatcherservlet中,并配置相关的bean,完成之后调用initServletBean来 创建servletwebApplicationContext,其中initServletBean方法在FrameworkServlet重写了,这些也是通用的模板方法模式。 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
protected final void initServletBean() throws ServletException {
....
try {
// 初始化springmvc所需要的容器
this.webApplicationContext = initWebApplicationContext();
// 扩展方法:实现为空
initFrameworkServlet();
}
}

protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;

if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}

if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}

if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}

return wac;
}

tomcat在启动的过程中会通过contextloaderlistener来创建一个webapplicationContext,然后将其作为一个字段存放在 servletContext中。 上面initWebApplicationContext的主要过程就是获取这个webapplicationcontext,并将其设置为当前容器的父容器,接下来 会调用onRefresh方法,这个方法最终会初始化我们的servlet,代码如下:

1
2
3
4
5
6
7
8
9
10
11
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

上面这一步会初始化就我们当前容器中所需要的一些java bean了,这里是子容器java bean的初始化。

处理请求

续接上文,我们已经大概清楚dispatcherservlet的初始化流程,以及初始化的时候都会初始化哪些资源了, 接下来我们看一下一个请求的处理过程。我们知道servlet请求的处理起始于service方法(service方法 可以认为是一个门面类,最终请求的处理肯定是落到doGet、doPost等方法中),在dispatcherservlet中 该方法的实现在FrameworkServlet类中。所有的处理最终都会经过的方法FrameworkServlet的processRequest:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
.....

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request);

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor());

initContextHolders(request, localeContext, requestAttributes);

try {
doService(request, response);
}......

finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}.....

publishRequestHandledEvent(request, response, startTime, failureCause);
}
}

上面代码是final类型的,可以看到请求的处理过程大致如下:

  • 调用initContextHolders将获取到的请求相关的参数绑定到线程上
  • 调用doService方法处理请求
  • 重置与线程绑定的信息,然后发布请求处理完成的事件

可以看到核心代码还在doService方法中(在dispatcherServlet中实现的),doService最终会进入doDispatch方法中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

try {
ModelAndView mv = null;
Exception dispatchException = null;

try {
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);

// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null || mappedHandler.getHandler() == null) {
noHandlerFound(processedRequest, response);
return;
}

// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (logger.isDebugEnabled()) {
logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified);
}
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}

if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

if (asyncManager.isConcurrentHandlingStarted()) {
return;
}

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv);
}.....
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}.....
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}

上面代码的大致流程是:

  • 通过getHandler从HandleMapping找到该请求对应的Handler

  • 通过getHandlerAdapter获取找到对应的HandlerAdapter

  • 判断是否有拦截器,如果有执行applyPreHandle

  • handlerAdapter会进行处理请求并返回ModelAndView,此处就会将请求发送到对应的controller

  • 接下来判断是否有后置拦截器,如果有则调用applyPostHandle进行增强

  • 最后会调用processDispatchResult,这个方法又分为以下几个步骤:

    • 通过viewResolver获取响应的View(视图)
    • 根据获取的视图进行渲染页面,具体的就是调用view的render方法进行页面的渲染

相关的流程图如下:

销毁

销毁流程比较简单,如下:

1
2
3
4
5
6
7
public void destroy() {
getServletContext().log("Destroying Spring FrameworkServlet '" + getServletName() + "'");
// Only call close() on WebApplicationContext if locally managed...
if (this.webApplicationContext instanceof ConfigurableApplicationContext && !this.webApplicationContextInjected) {
((ConfigurableApplicationContext) this.webApplicationContext).close();
}
}

其实就是关闭当前容器,因此也可以知道,只有我们的容器关闭了,dispatcherservlet才会销毁

小结

上面对spring mvc的流程进行了初步的分析,方便后面回顾,到此Spring相关的东西可以告一段落了。