SpringMVC基本原理分析
小橘子🍊

前言

本文是关于 SpringMVC 实现原理分析的文章,这之前还写了两篇关于 Spring AOP 和事务机制实现原理的文章,总的来说 SpringMVC 的执行流程稍微复杂些,但是根本的分析方法其实还是和上两篇文章差不多的,所以如果你对 spring 源码分析不知道如何开始的话,那么我还是建议先去看看上两篇文章,因为分析的方式基本差不多,所以本文也就从简进行说明,细节的部分也不会去深究,另外对于实现中的视图解析部分,我们是以 jsp 页面为例的,所以它的渲染是由 tomcat 所完成的,不属于本文分析的内容,如果采用的是其它类型的页面,比如 ftl,那么其渲染就是交由 FreeMarker 视图解析器完成的,渲染的过程我也没深究,就只是点到即可,如果对此感兴趣,可自行查找相关文章进行理解。

示例程序

同样的示例程序已经提交到我的 github,需要可以自行获取,而且里边添加了一些自己在分析实现原理时便于自己理解的注释。

pojo 类 top.aprilyolies.example.mvc.User

1
2
3
4
5
6
7
8
9
10
11
public class User {
private String name;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}
}

controller 类 top.aprilyolies.example.mvc.UserController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/hello")
public ModelAndView hello() {
System.out.println("accessed....");
User user = new User();
user.setName("eva");
User user1 = new User();
user1.setName("johnson");
ArrayList<User> users = new ArrayList<>();
users.add(user);
users.add(user1);
ModelAndView mv = new ModelAndView("userlist", "users", users);
return mv;
}
}

spring 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.3.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.3.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.3.xsd ">

<!-- @Controller注解扫描 -->
<context:component-scan base-package="top.aprilyolies.example.mvc"/>

<!-- 注解驱动:替我们显示的配置了最新版的注解的处理器映射器和处理器适配器 -->
<mvc:annotation-driven/>

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
</beans>

web 配置文件 web.xml

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
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<display-name>spring</display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>springMvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<!-- 在tomcat启动的时候就加载这个servlet -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springMvc</servlet-name>
<!--
*.action 代表拦截后缀名为.action结尾的
/ 拦截所有但是不包括.jsp
/* 拦截所有包括.jsp
-->
<url-pattern>/</url-pattern>
</servlet-mapping>

</web-app>

很简单的示例程序,但是对于分析 springMVC 的基本实现原理已经足够了,程序启动后,访问对应的请求链接就能在页面获取需要的信息了。

WebApplicationContext 的初始化

我们的程序是从 tomcat 启动的,它其实就是一个 servlet 程序容器,管理着 servlet 的创建、初始化、调用和销毁。在这个 servlet 容器的声明周期中有一项就是在容器的创建和销毁时完成对应监听器方法的调用,如下面这个接口所示,注意这个接口不是我们定义的,而是属于 servlet 规范中的项。我们需要做的就是遵循此规范,完成自己的逻辑。

javax.servlet.ServletContextListener

1
2
3
4
5
6
7
8
public interface ServletContextListener extends EventListener {

public default void contextInitialized(ServletContextEvent sce) {
}

public default void contextDestroyed(ServletContextEvent sce) {
}
}

了解这一点后,我们看 web.xml 文件,因为它就是直接跟 tomcat 相关的配置。里边有一个 <listener/> 标签,它其实就是上边所说的监听器,我们在里边配置了 org.springframework.web.context.ContextLoaderListener,那么在 tomcat 启动后,该监听器对应的接口方法一定会被调用,这就是我们分析的入口。定位到该类,可以知道它实现了 ServletContextListener 接口,所以该接口对应的方法就是我们分析的入口,该接口中有两个上边已经指出了,我们这里关心 contextInitialized 方法的实现如下。

1
2
3
4
5
6
7
/**
* Initialize the root web application context.
*/ // 获取 context class 的类型,然后通过它来构建 WebApplicationContext 实例,让其持有 ServletContext,设置了 spring 配置文件的位置,完成部分属性的替换和设置,然后刷新和启动 ConfigurableWebApplicationContext
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

参数的 event.getServletContext() 其实就是获取的 tomcat 容器对应的上下文环境,里边有 tomcat 容器相关的很多信息。进入 initWebApplicationContext 方法,从方法名我们就知道是初始化 WebApplicationContext,注意这个上下文环境就是跟我们的 spring 容器相关了。方法的的实现如下,方法的接口很清晰,我在关键代码处做出了注释,总结下来就是获取我们要创建的 spring 容器 context class 的类型,然后通过它来构建 ApplicationContext 实例,它持有 ServletContext 实例,然后就是设置了 spring 配置文件的位置,完成部分属性的替换和设置,然后刷新和启动 ApplicationContext。

注意此时启动的 ApplicationContext,我们在 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 此行代码中,让 servlet 上下文环境持有了本 ApplicationContext,它的属性名是 org.springframework.web.context.WebApplicationContext.ROOT,它将会在后面使用到。

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
 */	// 获取 context class 的类型,然后通过它来构建 WebApplicationContext 实例,让其持有 ServletContext,设置了 spring 配置文件的位置,完成部分属性的替换和设置,然后刷新和启动 ConfigurableWebApplicationContext
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { // 参数是 tomcat 中的实例
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException( // 检查可能的重复 application context
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}

servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();

try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) { // 获取 context class 的类型,然后通过它来构建 WebApplicationContext 实例
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
} // ConfigurableWebApplicationContext 持有 ServletContext,设置了 spring 配置文件的位置,完成部分属性的替换和设置,然后刷新和启动 ConfigurableWebApplicationContext
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
} // 将启动的 spring context 和 servlet context 绑定,避免重复启动
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}

if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}

return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}

ContextLoaderListener 类的核心作用就是上文所说的那样,细节部分我们不作深入探讨,但是在我 github 上的源码中,还是有添加部分注释,需要的可以自行查看。

DispatcherServlet 说明

在我们的 web.xml 文件中,除了上文指出的 ContextLoaderListener 外,在我们的 <servlet/> 标签中还指定了一个 DispatcherServlet 类,很明显它是一个 servlet 类,它的初始化、使用和销毁将会交给 servlet 容器来管理。我们定位到该类,来看看它的继承体系,以及对应的生命周期方法。

DispatcherServlet 继承体系

DispatcherServlet 类的继承体系如上图,可以知道它继承自 HttpServlet,这是 Servlet 规范中的类,所以我们的分析也就是止于此类。而对于生命周期方法,我们就需要重点关注的点,这里就是 HttpServletBean#init 方法,代码如下。

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
// 对于我们的 Servlet 而言,优先调用的是该方法
@Override // 就这里而言,好像就是将 web.xml 中 servlet 对应的属性值设置到当前 servlet 中,初始化 ApplicationContext,会触发监听器事件,导致标签解析中的 bean 被使用到 Dispatcher Servlet 中
public final void init() throws ServletException { // 注意 ContextLoaderListener 只是构建的 root ApplicationContext

// Set bean properties from init parameters. // 获取 ServletConfig 中的 params,添加到 propertyValueList,如果缺少必须参数,报错
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);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}

// Let subclasses do whatever initialization they like.
initServletBean(); // 初始化 ApplicationContext,会触发监听器事件,导致标签解析中的 bean 被使用到 Dispatcher Servlet 中
}

上边方法的第一行构建了一个 ServletConfigPropertyValues 类,需要注意在构建过程中,会完成相关属性的检测,如果缺少必要的属性,将会报错,实现代码如下。

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
 */	// 获取 ServletConfig 中的 params,添加到 propertyValueList,如果缺少必须参数,报错
public ServletConfigPropertyValues(ServletConfig config, Set<String> requiredProperties)
throws ServletException {

Set<String> missingProps = (!CollectionUtils.isEmpty(requiredProperties) ?
new HashSet<>(requiredProperties) : null);

Enumeration<String> paramNames = config.getInitParameterNames();
while (paramNames.hasMoreElements()) {
String property = paramNames.nextElement();
Object value = config.getInitParameter(property);
addPropertyValue(new PropertyValue(property, value)); // 将 PropertyValue 添加到 propertyValueList,覆盖式
if (missingProps != null) {
missingProps.remove(property);
}
}

// Fail if we are still missing properties.
if (!CollectionUtils.isEmpty(missingProps)) {
throw new ServletException(
"Initialization from ServletConfig for servlet '" + config.getServletName() +
"' failed; the following required properties were missing: " +
StringUtils.collectionToDelimitedString(missingProps, ", "));
}
}

接着 HttpServletBean#init 方法中我们需要注意的就是 initServletBean(); 这行,它的具体实现是由子类实现的,代码去掉了日志相关的信息如下,核心就是两行,就本例而言 initFrameworkServlet(); 是空实现,所以就只剩下一行要分析了。

FrameworkServlet#initServletBean

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override	// 初始化 ApplicationContext,会触发监听器事件,导致标签解析中的 bean 被使用到 Dispatcher Servlet 中
protected final void initServletBean() throws ServletException {
long startTime = System.currentTimeMillis();

try { // 初始化 ApplicationContext,会触发监听器事件,导致标签解析中的 bean 被使用到 Dispatcher Servlet 中
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet(); // 空
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
}

initWebApplicationContext(); 方法的实现如下,第一行是获取 rootContext,注意这里获取的 context 其实就是我们上文指出的缓存在 ServletContext 中的那个 ApplicationContext,它是在我们的 ContextLoaderListener 类中完成构建并缓存到 ServletContext 中的。本例中中间那个 if 判断不成立,所以直接跳过。接着就是尝试从环境中获取 ApplicationContext,本例中也是不存在的,所以直接就会到 createWebApplicationContext(rootContext); 这行代码,从名字我们就能知道是手动创建 ApplicationContext,话说我们不是在 ContextLoaderListener 中已经创建了一个 ContextLoaderListener 嘛?为什么这里又创建了一个呢?而且从后边的实现中可以知道,构建方式也跟 ContextLoaderListener 中的构建方式差不多,我这里也没太看懂它的意思,还是先看 createWebApplicationContext(rootContext); 方法的实现吧。

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
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext = // 获取当前 servlet context 对应的那个 WebApplicationContext
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); // 根据 rootContext 创建 WebApplicationContext
}

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.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}

if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}

return wac;
}

createWebApplicationContext(rootContext); 方法实现根本是调用的如下方法,先是获取 ApplicationContext 对应的类,然后由该 class 来创建 ApplicationContext 实例。注意新创建出来的 ApplicationContext 它持有了 ServletEnviroment 实例,将 ContextLoaderListener 类中创建的 ApplicationContext 作为了自己的 parent 字段,同时还持有了我们 spring 配置文件的位置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 获取 ContextClass,据此创建了一个 ApplicationContext,它持有我们 spring 配置文件的位置,同时将参数的 ApplicationContext 作为父 ApplicationContext,刷新配置和启动
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class<?> contextClass = getContextClass();
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);

wac.setEnvironment(getEnvironment());
wac.setParent(parent);
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation); // 持有 spring 配置文件
} // 刷新配置和启动
configureAndRefreshWebApplicationContext(wac);

return wac;
}

接着就是调用了 configureAndRefreshWebApplicationContext(wac); 方法,该方法对新构建的 ApplicationContext 进行了一定的配置,最后就是调用了 ApplicationContext 的刷新方法,这样我们需要的 spring 容器就算是正式启动了。在对 ApplicationContext 进行配置的过程中,我们需要一点就是 wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener())); 这行代码向 ApplicationContext 注册了一个监听器,那么在 ApplicationContext 的刷新启动过程中,将会触发该监听器的逻辑。

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
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}

wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}

postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}

定位到上边代码中所说的注册的那个监听器,代码如下,内容很简单,它根本是调用的 DispatcherServlet#initStrategies 方法,该方法中就是将 spring 容器中标签解析出来的那些 bean 逐个的添加到当前类,也就是 DispatcherServlet 中。这样在拦截到请求后就能够根据这些 bean 来完成相应的操作了,至此 springMVC 的启动流程分析就算完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
FrameworkServlet.this.onApplicationEvent(event);
}
}

// 完成一些 bean 的填充,主要就是在我们的标签解析中得到的那些 bean,还有视图解析器
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}

关于 DispatcherServlet 类,我们还要关注有哪些方法是实现或者重写的 sevlet 规范中的方法,我将它们列出来。
FrameworkServlet#serviceFrameworkServlet#doTraceFrameworkServlet#doOptionsFrameworkServlet#doDeleteFrameworkServlet#doPutFrameworkServlet#doPostFrameworkServlet#doGet 这七个方法都是完成了对于父类方法的重写,如果对于 servlet 的执行流程比较清楚的话,那么不难知道当有请求来访问时,将会首先交给 service 方法处理,这是后边将要分析的内容,对于 DispatcherServlet 类,我们暂时知道这些就行。

<mvc:annotation-driven/> 标签解析

在我们的 spring 配置文件中,指定了 <mvc:annotation-driven/> 标签,虽然标签名好像有注解这个单词,但其实就源码分析的层面来看,跟注解时没有半点关系的。另外还有一个特殊的标签 <context:component-scan/>,它跟 ioc 的实现有关,其实就是让 spring 容器自动扫面指定包下的组件,完成组件的注册,这里就不做分析了,仅仅关注 <mvc:annotation-driven/> 标签。

根据上两篇文章的分析,我们可以很轻松的知道该标签的解析是在 AnnotationDrivenBeanDefinitionParser#parse 方法中完成的,关于标签解析已经说得太多了,所做的事情都差不多,这里也不例外,只是注册的 bean 稍多,所以代码量也非常多,没有太多分析价值,这里就不贴代码了。只是将注册的 bean 列出来,不再一行行看,只要心里明白做了啥事情就行,这些 bean 中的很大一部分都是在上文指出的那个 ContextRefreshListener 监听器中被 DispatcherServlet 所持有,用来完成对于请求的处理。就本例而言,注入这么多 bean,并不是全部使用到,我们也是使用到啥就看啥。

  • CompositeComponentDefinition

  • RequestMappingHandlerMapping (用来获取 HandlerExecutionChain,Controller 的请求路径和方法的映射关系保存在其中)

  • ConfigurableWebBindingInitializer

  • RequestMappingHandlerAdapter (用来验证 handler 是否可用)

  • ResponseStatusExceptionResolver

  • CompositeUriComponentsContributorFactoryBean

  • ConversionServiceExposingInterceptor

  • DefaultHandlerExceptionResolver

  • MappedInterceptor

  • ExceptionHandlerExceptionResolver

  • BeanNameUrlHandlerMapping

  • RequestHandlerAdapter (用来验证 handler 是否可用)

  • SimpleControllerHandlerAdapter (用来验证 handler 是否可用)

  • HandlerMappingIntrospector

请求处理流程

上文中已经指出了 DispatcherServlet 类中,有七个方法重写了 servlet 规范类的方法,如果有请求消息,那么这个七个方法中的 FrameworkServlet#service 将会首先被调用。我们在其中打一个断点,通过 debug 的方式来对请求的流程进行跟踪。调用栈信息如下,可以知道现在程序时停在了我们的 FrameworkServlet#service 方法上,而它的上一个调用就是 HttpServlet#service 方法,这是 servlet 规范类中的方法,代码如下,可以知道它最后调用了它的一个重载方法,而我们的 DispatcherServlet 类对其进行了重写,所以现在执行的就是 FrameworkServlet#service 方法。

Servlet 调用栈信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {

HttpServletRequest request;
HttpServletResponse response;

try {
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
} catch (ClassCastException e) {
throw new ServletException(lStrings.getString("http.non_http"));
}
service(request, response);
}

FrameworkServlet#service 方法中的内容如下,它优先对方法的请求类型进行了判断,如果是 HttpMethod.PATCH 方法,那么就会执行 if 分支,否则执行 else 分支。本例中走 else 分支,可以看到它又调用了被重写的方法。

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void service(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response); // 针对 PATCH 请求的处理
}
else {
super.service(request, response);
}
}

我们大致看看这个被重写的方法的实现,内容如下,可以看到就是七个分支,对应七种请求,而我们的 DispatcherServlet 完成了对其中六种方法的重写,对应本例来说,我们的请求是 GET 方式,所以就会走 METHOD_GET 分支,可以看到其根本还是调用了 doGet 方法,我们对其完成了重写,所以接下来就会执行到我们的 FrameworkServlet#doGet 方法。

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
 protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException{
String method = req.getMethod();

if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}

} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);

} else if (method.equals(METHOD_POST)) {
doPost(req, resp);

} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);

} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);

} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);

} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);

} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//

String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);

resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}

FrameworkServlet#doGet 方法根本又是调用的 FrameworkServlet#processRequest 方法,可以看到前半部分都是一些准备操作,我没有去细看都准备了哪些内容,感兴趣可以自行分析。完成相关信息的准备后,就是执行 doService(request, response); 这句代码。里边大部分都是准备工作,就是向参数 HttpServletRequest 中添加了一些属性,而属性值就是来自于 DispatcherServlet,知道这点后我们继续跟进,可以看到就是调用了 DispatcherServlet#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
protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

long startTime = System.currentTimeMillis();
Throwable failureCause = null;

LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
LocaleContext localeContext = buildLocaleContext(request); // 根据 request 构建 LocaleContext,里边包含了一些请求相关的信息

RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes requestAttributes = buildRequestAttributes(request, response, previousAttributes); // 其实就是封装了 request 和 response

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new RequestBindingInterceptor()); // 注册了一个绑定拦截器
// 将 LocaleContext 和 RequestAttributes 分别保存到 LocaleContextHolder 和 RequestContextHolder
initContextHolders(request, localeContext, requestAttributes);

try {
doService(request, response);
}
catch (ServletException | IOException ex) {
failureCause = ex;
throw ex;
}
catch (Throwable ex) {
failureCause = ex;
throw new NestedServletException("Request processing failed", ex);
}

finally {
resetContextHolders(request, previousLocaleContext, previousAttributes);
if (requestAttributes != null) {
requestAttributes.requestCompleted();
}
logResult(request, response, failureCause, asyncManager);
publishRequestHandledEvent(request, response, startTime, failureCause); // 完成事件的通知
}
}

DispatcherServlet#doDispatch 方法的内容如下,它可以算是 springMVC 的核心逻辑实现。为了节省篇幅我将异常捕获和 finally 代码块去掉了,感兴趣的可以自行分析,不影响我们分析 spirngMVC 的实现逻辑。第一个要注意的代码就是 checkMultipart(request); 根据我的理解,它应该是跟文件上传时的处理有关,本例不会涉及,只需要知道这个点就行,用到了可以再深入去看。

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
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 获取 request 中的 WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

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

try {
processedRequest = checkMultipart(request); // 如果指定了 Multipart 处理器,进行处理
multipartRequestParsed = (processedRequest != request); // 是否被 multipart 处理的标志
// 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod,再构建 execution chain 返回
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == 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)) { // Get 或者 HEAD 方法
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
// 调用拦截器的前置处理器
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 核心就是获取 ModelAndView,模型来自用户执行逻辑的代码,视图也是
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

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

applyDefaultViewName(processedRequest, mv);
mappedHandler.applyPostHandle(processedRequest, response, mv); // 应用后置处理器
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
} // 处理分发结果
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}

第二个要留意的点就是 getHandler(processedRequest); 方法的执行,它的实现如下,从方法的主干来看就是从 handlerMappings 中获取 HandlerExecutionChain,如果结果不为 null,就直接返回。那么 handlerMappings 中都是啥呢?通过 debug,我将里边的内容展示出来,可以看到它就是我们在标签解析中注册到 spring 容器中的 bean,所以我们这里也就能知道他们是用来获取对应的 HandlerExecutionChain。继续跟进去,看是如何得到 HandlerExecutionChain 的。

1
2
3
4
5
6
7
8
9
10
11
12
@Nullable	// 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod,再构建 execution chain 返回
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) { // 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod,再构建 execution chain 返回
return handler;
}
}
}
return null;
}
handlerMappings 集合的内容

上边的 handlerMappings 集合中有两个实例,首先处理第一个,也就是 RequestMappingHandlerMapping 实例,代码如下。

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
@Nullable	// 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod,再构建 execution chain 返回
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request); // 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// 将 handler 构建为 chain,同时加入了标签解析时注册的 MappedInterceptor 中的 interceptor
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
logger.debug("Mapped to " + executionChain.getHandler());
}

if (hasCorsConfigurationSource(handler)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}

return executionChain;
}

第一句就是获取 handler 对吧,它根本是调用的 AbstractHandlerMethodMapping#getHandlerInternal 方法,我将代码贴在下边了。第一句是得到了 lookupPath,根据方法名可以知道就是一个通过 UrlPathHelper 获取 HttpServletRequest 对应的 LookupPath 的过程,我们不必深究,只需要看其结果如下图,可以知道就是我们请求的链接路径信息对吗?好,拿到这个路径信息后,第二行代码将其填充到了 HttpServletRequest 的属性中了。再接着就是执行 lookupHandlerMethod(lookupPath, request); 这行代码。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override // 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try { // 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
lookupPath 的内容

lookupHandlerMethod(lookupPath, request); 方法又有点长,但逻辑还算清晰,首先我们创建了一个 List 集合,元素是 Match 类。接着执行 addMatchingMappings(directPathMatches, matches, request); 这行代码,里边的代码内容不再贴了,核心逻辑就是从 directPathMatches 中获取和 request 匹配的项,然后将得到的匹配项封装为 Match 实例,再追加到 List 集合中,那么这个集合中存放的信息是啥呢??我把内容贴出来,可以看出来其实里边存放的不就是当前请求对应的 controller 类的对应方法吗??知道这点后,那么后边的代码又是干啥呢??其实只需要根据 Match bestMatch = matches.get(0); 这句代码就能猜出意思来了,也就是说获取的 matches 集合可能不止一个 Match 元素,那么在对其按照一定规则完成排序后,第一个元素就是最匹配项项,我们根本也就是要拿到这个 Match 实例,最后再通过 return bestMatch.handlerMethod; 这句代码返回最佳匹配项的 handlerMethod,里边有当前请求对应的 controller 的方法信息。

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
@Nullable	// 根据 lookupPath 获取 matches 集合,对其按照匹配度排序,拿到最匹配的项,获取其中的 HandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); // 根据 lookupPath 获取 matches 集合
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

if (!matches.isEmpty()) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator); // 对 matches 集合进行排序
Match bestMatch = matches.get(0); // 获取最匹配的项
if (matches.size() > 1) {
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod; // 拿到最匹配项的 HandlerMethod
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
matches 集合的内容

拿到这个 handler 后,我们退回到构建 HandlerExecutionChain 的方法,即 AbstractHandlerMapping#getHandler 中。既然是构建 ExecutionChain,那自然不会只有一个 handler 吧,这一步就是通过该方法中的 getHandlerExecutionChain(handler, request); 这行代码完成的。里边就是将获取的 handler 构建为 chain,同时加入了标签解析时注册的 MappedInterceptor 中的 interceptor。代码就不再贴了,自行查看即可。得到这个 HandlerExecutionChain,后边的内容基本没有啥紧要的操作,然后就是返回这个 HandlerExecutionChain。

再回到我们获取 HandlerExecutionChain 的入口处,即 DispatcherServlet#doDispatch 方法的 getHandler(processedRequest); 这行代码,返回结果就是我们的 HandlerExecutionChain,紧接着又调用了 getHandlerAdapter(mappedHandler.getHandler()); 这行代码,里边的实现如下,核心逻辑就是遍历 handlerAdapters 集合,从中获取出能够和参数 handler 匹配的处理器适配器,那么这个 handlerAdapters 中都有哪些适配器呢?我将内容通过下图展示出来,可以看到其中的两项都是我们在标签解析中注册的 bean。具体的匹配逻辑也就不深入看了,直接返回,可以知道本例中,得到的是 RequestMappingHandlerAdapter 实例。

1
2
3
4
5
6
7
8
9
10
11
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
throw new ServletException("No adapter for handler [" + handler +
"]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
handlerAdapters 集合的内容

再往下走就是 applyPreHandle(processedRequest, response) 这行代码,这里就是对于得到的 HandlerExecutionChain 调用前置处理方法,属于功能拓展部分,大致理解就好。然后就是 mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); 这句,mv 代表就是 springMVC 概念中的 model 和 view。

该方法根本还是调用的下边这个方法,该方法大部分内容都是进行的一些准备工作,这也不是我们应该关注的内容,只需要注意其中的两个核心逻辑,第一个就是 invokeAndHandle(webRequest, mavContainer); 这句。

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
@Nullable	// 核心就是获取 ModelAndView,模型来自用户执行逻辑的代码,视图也是
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try { // 数据绑定工厂
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 模型工厂
// 根据 HandlerMethod 构建 ServletInvocableHandlerMethod
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) { // 参数方法解析器填充到 ServletInvocableHandlerMethod
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) { // 返回值处理器添加到 ServletInvocableHandlerMethod
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory); // 设置数据绑定工厂
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 参数名发现器

ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // 模型视图容器
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 异步 web 请求
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
// web 异步管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// 触发用户 bean 的对应方法,完成对于返回结果的处理
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
// 返回模型和视图
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}

invokeAndHandle(webRequest, mavContainer); 实现如下,总的来说也只有两个核心逻辑,首先是 invokeForRequest(webRequest, mavContainer, providedArgs); 这句代码,它根本是调用了用户的逻辑代码,返回的就是对应方法的返回值,就本例而言,我们返回的是一个 ModelAndView 实例,调用的过程很简单,是通过反射方式进行的,不再给出代码,自行跟踪即可,然后就是对于这个返回结果来进行处理,它体现在 returnValueHandlers.handleReturnValue 这句。

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
// 触发用户 bean 的对应方法,完成对于返回结果的处理
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
// 触发用户 bean 的对应方法
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
setResponseStatus(webRequest);

if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}

mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}

returnValueHandlers.handleReturnValue 方法的实现如下,里边执行了 selectHandler 方法,选择的是啥呢??注意我们现在是调用的 returnValueHandlers 的方法,所以通过查看 returnValueHandlers 实例的内容就能知道是对什么进行选择。从下图我们知道,该实例持有了 15 个 handler,那么可以想到 selectHandler 方法就是从这个 15 个 handler 中选择能够对参数 returnValue 进行处理的合适的 handler 了。换句话来说,我们的用户代码这里返回的是 ModelAndView 实例,那么 selectHandler 方法就是从 15 个 handler 中选择一个能够对 ModelAndView 实例进行处理 handler。拿到这个 handler 后,就是对用户代码的结果进行处理,本例中拿到的 handler 是 ModelAndViewMethodReturnValueHandler 实例,接着看它的 handleReturnValue 方法。

1
2
3
4
5
6
7
8
9
10
@Override	// 根据 MethodParameter 从 returnValueHandlers 中选择合适的 handler,使用获得的 handler 处理响应结果
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
// 根据 MethodParameter 从 returnValueHandlers 中选择合适的 handler
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
} // 使用获得的 handler 处理响应结果
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
returnValueHandlers 集合的内容

ModelAndViewMethodReturnValueHandler#handleReturnValue 方法的实现如下,总的来说就是对参数 mavContainer 进行了各项属性的设置,拿本例来说,重要的属性就两个,setViewName(viewName) 方法是设置的视图名字 “userlist”,addAllAttributes(mav.getModel()) 方法设置的是模型信息,本例的内容见下图,这样通过 Handler 对用户代码返回结果进行处理的逻辑就很清楚了。记住一点,模型和视图信息都保存到了参数的 ModelAndViewContainer 中,好了我们逐步返回,看看这个 ModelAndViewContainer 哪里被使用。

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
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}

ModelAndView mav = (ModelAndView) returnValue;
if (mav.isReference()) {
String viewName = mav.getViewName();
mavContainer.setViewName(viewName);
if (viewName != null && isRedirectViewName(viewName)) {
mavContainer.setRedirectModelScenario(true);
}
}
else {
View view = mav.getView();
mavContainer.setView(view);
if (view instanceof SmartView && ((SmartView) view).isRedirectView()) {
mavContainer.setRedirectModelScenario(true);
}
}
mavContainer.setStatus(mav.getStatus());
mavContainer.addAllAttributes(mav.getModel());
}
模型的内容

回退的位置就是 RequestMappingHandlerAdapter#invokeHandlerMethod 方法的 invokeAndHandle(webRequest, mavContainer); 这行代码处,我们在这里边完成了用户代码逻辑的调用,同时又通过 handler 完成了对于返回结果的处理,最终就是将模型和视图信息保存到了 ModelAndViewContainer。再往下走就是 getModelAndView(mavContainer, modelFactory, webRequest); 这句,参数 mavContainer 中此时已经持有了我们的模型和视图信息,跟进去看看做了啥操作。代码如下,细节也不管了,可以知道核心就是构建了一个 ModelAndView 实例,然后将 ModelAndViewContainer 中的模型和视图信息都填充到了新构建的 ModelAndView 中返回。既然返回,我们就只需要不断地后退,看在哪里会使用我们现在得到的这个 ModelAndView,其结果是 DispatcherServlet#doDispatch 方法的 ha.handle(processedRequest, response, mappedHandler.getHandler()); 这行代码处,也就是说我们是通过最初得到的处理器适配器来得到 ModelAndView 实例的。紧接着再调用 HandlerExecutionChain 的后置处理方法,思想跟前置处理方法差不多,了解就好。接着就是使用这个我们好不容易得到的 ModelAndView 实例了,它在 DispatcherServlet#processDispatchResult 方法中完成,该方法做了很多附加操作,但核心其实就只有 render(mv, request, response); 这一句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel(); // 模型 map,里边存放的是,数据 key 和数据 value
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus()); // ModelAndView 里边包含了模型和视图信息
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}

render 方法具体的实现如下,核心逻辑就只有两个,获取视图实例,然后使用该实例进行渲染。先看视图实例如何获取吧。

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
 */	// 尝试根据环境中的视图解析器来创建视图,优先从缓存中获取视图,如果没有的话,进行创建(判断视图名字,redirect: 和 forward: 单独处理,否则获取设置的 view class 对应的视图实例,完成相关信息的设置,应用生命周期方法返回)并返回。
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception { // 获取全部的模型,将模型以参数的形式添加到 request 中,拿到跳转的路径,获取 RequestDispatcher,进行请求转发
// Determine locale for request and apply it to the response.
Locale locale = // 地区信息
(this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
response.setLocale(locale);

View view;
String viewName = mv.getViewName(); // 视图名字
if (viewName != null) {
// We need to resolve the view name.
view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
if (view == null) { // 尝试根据环境中的视图解析器来创建视图,优先从缓存中获取视图,如果没有的话,进行创建(判断视图名字,redirect: 和 forward: 单独处理,否则获取设置的 view class 对应的视图实例,完成相关信息的设置,应用生命周期方法返回)并返回。
throw new ServletException("Could not resolve view with name '" + mv.getViewName() +
"' in servlet with name '" + getServletName() + "'");
}
}
else {
// No need to lookup: the ModelAndView object contains the actual View object.
view = mv.getView();
if (view == null) {
throw new ServletException("ModelAndView [" + mv + "] neither contains a view name nor a " +
"View object in servlet with name '" + getServletName() + "'");
}
}

// Delegate to the View object for rendering.
if (logger.isTraceEnabled()) {
logger.trace("Rendering view [" + view + "] ");
}
try {
if (mv.getStatus() != null) {
response.setStatus(mv.getStatus().value());
} // 获取全部的模型,将模型以参数的形式添加到 request 中,拿到跳转的路径,获取 RequestDispatcher,进行请求转发
view.render(mv.getModelInternal(), request, response);
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug("Error rendering view [" + view + "]", ex);
}
throw ex;
}
}

视图实例获取是在 DispatcherServlet#resolveViewName 方法中完成的,差不多就是通过视图解析器完成的,本例中视图解析器只有一个就是 spring 配置文件中给出的那个 InternalResourceViewResolver。它是如何获取我们需要的视图实例的代码,本文就不在深究了,但是在我的源码中,还是有添加一些注释,感兴趣的可以自己了解,这里只给出我们获取的视图实例类型为 JstlView。

获取视图实例只是上边 DispatcherServlet#render 方法的第一个核心逻辑,第二个就是通过现在获取的这个 JstlView 视图实例来完成渲染操作,它根本是调用的 InternalResourceView#renderMergedOutputModel 方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Nullable	// 尝试根据环境中的视图解析器来创建视图,优先从缓存中获取视图,如果没有的话,进行创建(判断视图名字,redirect: 和 forward: 单独处理,否则获取设置的 view class 对应的视图实例,完成相关信息的设置,应用生命周期方法返回)并返回。
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
Locale locale, HttpServletRequest request) throws Exception {

if (this.viewResolvers != null) {
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) { // 尝试从缓存中获取视图,如果没有的话,进行创建(判断视图名字,redirect: 和 forward: 单独处理,否则获取设置的 view class 对应的视图实例,完成相关信息的设置,应用生命周期方法返回)并返回。
return view;
}
}
}
return null;
}

InternalResourceView#renderMergedOutputModel 方法代码实现如下,根据我们使用原生 servlet 的经验,这段代码中,我们应该关注的就三个,第一就是 exposeModelAsRequestAttributes(model, request); 这句代码是将模型信息填充到 HttpServletRequest 的属性中,这样 jsp 页面能够通过相应的语法来完成值的获取。第二个就是 RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath); 这句代码,注意这里的 dispatcherPath,它的值是 /WEB-INF/jsp/userlist.jsp 这个正好就是 controller 中指定的视图名称对应的 jsp 页面,我们的原生 servlet 使用过程中也是让 RequestDispatcher 持有了 jsp 的路径信息。最后就是 rd.forward(request, response); 这行代码的调用,内部的逻辑已经不属于我们分析的范畴了,但是我们可以知道这句执行完成后就会通过 jsp 生成 servlet,然后完成响应信息到客户端的发送。这样整个 springMVC 的执行流程就算分析完毕了。

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
@Override	// 将模型以参数的形式添加到 request 中,拿到跳转的路径,获取 RequestDispatcher,进行请求转发
protected void renderMergedOutputModel(
Map<String, Object> model, HttpServletRequest request, HttpServletResponse response) throws Exception {

// Expose the model object as request attributes.
exposeModelAsRequestAttributes(model, request); // 将模型全部添加到 request 属性中

// Expose helpers as request attributes, if any.
exposeHelpers(request);

// Determine the path for the request dispatcher.
String dispatcherPath = prepareForRendering(request, response);

// Obtain a RequestDispatcher for the target resource (typically a JSP).
RequestDispatcher rd = getRequestDispatcher(request, dispatcherPath);
if (rd == null) {
throw new ServletException("Could not get RequestDispatcher for [" + getUrl() +
"]: Check that the corresponding file exists within your web application archive!");
}

// If already included or response already committed, perform include, else forward.
if (useInclude(request, response)) {
response.setContentType(getContentType());
if (logger.isDebugEnabled()) {
logger.debug("Including [" + getUrl() + "]");
}
rd.include(request, response);
}

else {
// Note: The forwarded resource is supposed to determine the content type itself.
if (logger.isDebugEnabled()) {
logger.debug("Forwarding to [" + getUrl() + "]");
}
rd.forward(request, response); // 这里就是进行请求转发了,也就是跳转到我们指定的那个视图
}
}

整个的 springMVC 请求处理过程还算是很长的,这里进行一下总结,首先我们的 DispatcherServlet 重写了 HttpServlet 的 service 方法,那么在收到请求时,首先会调用该方法,而该方法实际又是调用了父类 HttpServlet 中的 service 方法,在该方法中,它会根据请求的类型来判断调用那个 doXXX 方法。我们的 DispatcherServlet 完成了对 doXXX 方法的重写,所以根本还是会调用 DispatcherServlet 的 doXXX 重写方法。就本例而言,我们是调用的 doGet 方法,此方法根本是调用的 DispatcherServlet#doDispatch 方法,在该方法中,我们会首先根据 request 信息从处理器映射集合中得到合适的处理器映射器,再从处理器映射器保存的处理器信息中获取最佳的处理器。接着就是根据获得的处理器来得到匹配的处理器适配器,利用这个处理器适配器,我们完成了用户代码的调用,对于调用返回的结果,我们选择了合适的返回值处理器完成了对于结果的处理,就本例而言,我们的结果处理器就是将结果的模型和视图信息填充到了 ModelAndViewContainer 中。接着就是根据 ModelAndViewContainer 构建 ModelAndView 实例,拿到这个 ModelAndView 实例后进行渲染操作,核心逻辑就两个,得到合适的视图实例,然后通过该实例完成渲染操作,以上就是整个 springMVC 的执行流程。