MVC ServletContainerInitializer

警告
本文最后更新于 2020-12-10,文中内容可能已过时。

spring源码系列文章,示例代码的中文注释,均是 copy 自 https://gitee.com/wlizhi/spring-framework

链接中源码是作者从 github 下载,并以自身理解对核心流程及主要节点做了详细的中文注释。


在web容器启动时为提供给第三方组件机会做一些初始化的工作,例如注册 servlet 或者 filter 等,servlet 规范中通过 ServletContainerInitializer 实现此功能。

伴随着 ServletContainerInitializer 一起起作用的还有 @HandlesTypes 注解,通过这个注解会将指定的类注入到 ServletContainerInitializer 的 onStartup 方法参数中。

ServletContainerInitializer 是通过 SPI 的方式扫描加载的,在对应 jar 包的 META-INF/services 下,会扫描 javax.servlet.ServletContainerInitializer 文件,加载文件中定义的实现类。

SpringMvc 就是利用这种方式,完成 DispatcherServlet 的初始化工作。

在 spring-web 包 META-INF/services 下就有 javax.servlet.ServletContainerInitializer 文件,文件中是这么定义的:

org.springframework.web.SpringServletContainerInitializer

SpringServletContainerInitializer 源码如下:

 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
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {
		// 根据servlet规范,tomcat会在启动的时候,以SPI方式加载 services/javax.servlet.ServletContainerInitializer,并调用onStartup方法。
		// 而spring-web包下就定义了此类。所以服务器启动时,会执行到这里。

		// 这个列表封装了所有SPI加载的 WebApplicationInitializer 实例。
		List<WebApplicationInitializer> initializers = new LinkedList<>();

		if (webAppInitializerClasses != null) {
			for (Class<?> waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
				// 非接口、非抽象类,且是WebApplicationInitializer实现类的,将会反射创建对象。封装到上面定义的列表中。
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		// 排序,按照排序后的顺序,遍历、调用onStartup()。
		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

SpringServletContainerInitializer 类声明处定义了 @HandlesTypes(WebApplicationInitializer.class),web容器启动时,会调用 SpringServletContainerInitializer 的 onStartup(),并将项目中存在的所有 WebApplicationInitializer 实现类的 Class 对象,作为参数传递给 onStartup()。

遍历这个集合,将所有的实现类通过反射调用无参构造函数进行实例化。实例化后,得到一个实例列表,给这个列表中实例进行排序后,依次调用每个实例的 onStartup()。

我们可以实现 WebApplicationInitializer,或者继承它的子类,通过实现一些特定的方法,来达到在容器启动时,加载一些内容的目的。

首先看已经实现了一些方法的子类。以下是 AbstractDispatcherServletInitializer 中 onStartup() 源码:

1
2
3
4
5
6
7
8
9
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// 创建 RootApplicationContext,也就是spring父容器。
		// 后面的流程中,设置子容器的父容器时,就是通过servletContext作为桥梁,进行获取的。父容器实例化后,会注册到servletContext。
		super.onStartup(servletContext);
		registerDispatcherServlet(servletContext);
	}
}

主要调用了两个方法 super.onStartup()registerDispatcherServlet()


super.onStartup() 源码中调用了 registerContextLoaderListener(),源码如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

    protected void registerContextLoaderListener(ServletContext servletContext) {
		// 创建 RootApplicationContext,这里面会有一个回调 getRootConfigClasses。这个回调中获取了实现类方法中返回的配置类。
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			// 创建监听器,监听器中封装了ApplicationContext,将这个监听器封装到servletContext中
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
	}
}

创建了一个 ApplicationContext,这是spring父容器,后面会创建一个子容器。将其放到一个 ServletContextListener 监听器中。

创建 ApplicationContext 源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
		extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createRootApplicationContext() {
		// 这里获取到具体实现类中此方法返回的配置类型。
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			// 创建上下文对象,并将配置类注入到上下文对象中。
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}
}

这里调用了 getRootConfigClasses(),这是一个模板方法,如果我们通过继承这个类的方式,来重写这个方法,就可以将容器启动的配置类,加载到spring父容器中。

监听器的 contextInitialized() 方法中,会调用 initWebApplicationContext()。

 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
public class ContextLoader {

	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
		servletContext.log("Initializing Spring root WebApplicationContext");
		Log logger = LogFactory.getLog(ContextLoader.class);
        if (this.context == null) {
            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);
                }
                // TODO 调用了 refresh()
                configureAndRefreshWebApplicationContext(cwac, servletContext);
            }
        }
        // 将WebApplicationContext添加到servletContext中,后面子容器设置父容器时,就会通过这个键值进行获取。
        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);
        }
        // 省略...
        return this.context;
	
	}
}

这里重点有两个

  1. configureAndRefreshWebApplicationContext() 调用了 refresh() 方法,刷新容器。这时容器中已经封装了定义的配置类。会根据配置类进行递归扫描。

  2. servletContext.setAttribute(),将这个 ApplicationContext 保存到 ServletContext 中,这样,在子容器创建后,设置父容器,就可以从 ServletContext 中获取。


回到 onStartup(),在调用完 super.onStartup()之后,会调用 registerDispatcherServlet(),源码如下:

 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
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

    protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
		// 创建spring子容器。这里面通过回调方法 getServletConfigClasses。
		WebApplicationContext servletAppContext = createServletApplicationContext();
		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
		// 创建 DispatcherServlet。
		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
		// 将servlet添加到servletContext中
		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
		if (registration == null) {
			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
					"Check if there is another servlet registered under the same name.");
		}

		// 如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。
		// 如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,
		// 值越小,servlet的优先级越高,就越先被加载
		registration.setLoadOnStartup(1);
		registration.addMapping(getServletMappings());
		registration.setAsyncSupported(isAsyncSupported());

		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}

		customizeRegistration(registration);
	}
}

这里创建了子容器 WebApplicationContext 和 DispatcherServlet,子容器就保存在 DispatcherServlet 中。

DispatcherServlet.init() -> initServletBean() -> initWebApplicationContext()

initWebApplicationContext() 源码:

 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
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

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

		// TODO 注解方式激活MVC功能时,会走到这里
		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()) {
					// 设置父容器,这个父容器时从servletContext中获取的。
					// 父容器是在 ContextLoader.initWebApplicationContext(),调用完refresh()后设置到servletContext中的。
					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);
					}
					// TODO 配置、刷新WebApplicationContext,调用DispatcherServlet的init()
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		// 省略...

		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
				// 初始化 initHandlerMappings、initHandlerAdapters 等
				onRefresh(wac);
			}
		}
    
		if (this.publishContext) {
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}
}

首先获取 rootContext,然后获取到 DispatcherServlet 中已经封装的 WebApplicationContext,将 rootContext 设置为 WebApplicationContext 的父容器。然后调用 configureAndRefreshWebApplicationContext(),进而调用子容器的 refresh(),完成上下文的加载。

最后 onRefresh() 中完成了 DispatcherServlet 中很多必要组件的初始化,包括 initHandlerMappings、initHandlerAdapters 等。

获取父容器会调用到这里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public abstract class WebApplicationContextUtils {

    public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) {
		Assert.notNull(sc, "ServletContext must not be null");
		// 从 servletContext中获取父容器 WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
		Object attr = sc.getAttribute(attrName);
		// 省略...
		return (WebApplicationContext) attr;
	}
}

我们在以XML的方式配置基础的springmvc项目时,在web.xml中通常有两个重要配置:ContextLoaderListener 和 DispatcherServlet。而上面的流程,就创建了这两个核心配置的内容。

如果我们不用XML方式配置,而是通过自定义一个 WebApplicationInitializer 实现类的方式,就可以做到无XML来启动基于springmvc的项目,所有的配置通过注解方式来完成。