Spring源码分析 - Tomcat Servlet规范实现与Spring Web应用自动装配

本系列文章基于以下版本。

Spring Framework 版本:v5.2.8.RELEASE

Apache Tomcat 版本:v9.0.37

引子

以前做web应用开发,web.xml是必然要打交道的一个文件,通过它配置应用信息,各种servlets、filters和listeners等。

现在开发,已经见不到这个文件了。这意味着,有一种隐式的方式替代了显式的配置文件方式,来完成同样的工作。

这就是本文要讲的“Web应用自动装配”。

正题

Web应用自动装配,依赖于Servlet容器和Servlet开发框架的合作。前者实现Servlet规范,提供扩展服务,后者实现扩展服务,完成实际工作。

Servlet容器以Apache Tomcat来分析。

Servlet开发框架以Spring Framework来分析。

Tomcat Servlet规范实现

新的Servlet规范为 ServletContext 新增了若干接口。

源码文件(apache-tomcat-9.0.37-src\java\javax\servlet\ServletContext.java)

1
2
3
4
5
6
7
8
9
public interface ServletContext {

public ServletRegistration.Dynamic addServlet(String servletName, String className);
...
public FilterRegistration.Dynamic addFilter(String filterName, String className);
...
public void addListener(String className);
...
}

有了这些接口,就提供了通过代码配置servlets、filters和listeners等的能力,即web应用初始化。

什么时候怎么做呢?

Servlet规范提供了 ServletContainerInitializer

源码文件(apache-tomcat-9.0.37-src\java\javax\servlet\ServletContainerInitializer.java)

1
2
3
4
5
public interface ServletContainerInitializer {

void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}

Servlet容器会在启动的合适时机回调该接口实现类的 onStartup 方法,用户(此处为Servlet开发框架)实现该接口完成具体的初始化工作。

为了减少耦合,Tomcat采用了类似 Java SPI 的服务提供发现机制。

源码文件(apache-tomcat-9.0.37-src\java\org\apache\catalina\startup\WebappServiceLoader.java)

1
2
3
4
5
6
7
8
public class WebappServiceLoader<T> {
private static final String CLASSES = "/WEB-INF/classes/";
private static final String LIB = "/WEB-INF/lib/";
private static final String SERVICES = "META-INF/services/";

public List<T> load(Class<T> serviceType) throws IOException {
String configFile = SERVICES + serviceType.getName();
...

WebappServiceLoader 查找并加载 META-INF/services/ 下的服务实现。

源码文件(apache-tomcat-9.0.37-src\java\org\apache\catalina\startup\ContextConfig.java)

1
2
3
4
5
6
7
8
protected void processServletContainerInitializers() {

List<ServletContainerInitializer> detectedScis;
try {
WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
detectedScis = loader.load(ServletContainerInitializer.class);
} catch (IOException e) {
...

如此,就能找到 ServletContainerInitializer 接口的实现类,该实现类由Servlet开发框架提供,便将具体初始化工作转移到了用户方。

我们注意到 onStartup 有两个参数,第2个是 ServletContext,不用多说。第一个是做什么的?

根据前面描述,ServletContainerInitializer 是来做web 应用初始化工作的,它提供了一个Servlet容器到Servlet开发框架的桥梁。Servlet开发框架具体实现时,也会定义一些初始化接口,然后在 onStartup 时调用这些接口服务。

第一个参数就是Servlet容器通过类似ASM字节码解析方式,加载的Servlet开发框架的web应用初始化接口实现类集合。

这就是涉及到要筛选出相应的web应用初始化接口实现类,所以Servlet规范提供了 @HandlesTypes 注解。

Spring Framework扩展实现

Spring的 ServletContainerInitializer 实现类如下。

源码文件(spring-framework\spring-web\src\main\java\org\springframework\web\SpringServletContainerInitializer.java)

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 {

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...
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);
}
}
}
}

if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}

servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}

Spring的web应用初始化接口为 WebApplicationInitializer,通过 @HandlesTypes 注解告知Servlet容器筛选加载其实现类,并作为第1个参数传入 onStartup,依次回调实现类的 onStartup,完成web应用初始化工作。

Spring中,WebApplicationInitializer 有若干实现。

源码文件(spring-framework\spring-web\src\main\java\org\springframework\web\context\AbstractContextLoaderInitializer.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {

/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());

@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}

该实现会向 ServletContext 添加 ContextLoaderListener

源码文件(spring-framework\spring-web\src\main\java\org\springframework\web\context\ContextLoaderListener.java)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

public ContextLoaderListener(WebApplicationContext context) {
super(context);
}

@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}

@Override
public void contextDestroyed(ServletContextEvent event) {
closeWebApplicationContext(event.getServletContext());
ContextCleanupListener.cleanupAttributes(event.getServletContext());
}

ContextLoaderListener 的主要作用是使用传入的或创建新的Spring上下文,配置刷新该上下文,并保存到到 ServletContext

其他的一些实现类型提供了包括向 ServletContext 添加servlets, filters,创建Spring MVC上下文等功能。

Servlet容器启动时机

源码文件(apache-tomcat-9.0.37-src\java\org\apache\catalina\core\StandardContext.java)

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
protected synchronized void startInternal() throws LifecycleException {
// Notify our interested LifecycleListeners
fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
...

// Call ServletContainerInitializers
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry :
initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(),
getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}

// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
}

CONFIGURE_START_EVENT 会最终触发扫描加载 ServletContainerInitializer 的实现类(SpringServletContainerInitializer) 和 筛选加载 @HandlesType 注解指定的接口或注解(WebApplicationInitializer)实现类。

entry.getKey().onStartup 即回调 ServletContainerInitializer 实现类(SpringServletContainerInitializer)的 onStartup 方法。

listenerStart 会调用 ServletContextListener 实现类(ContextLoaderListener) 的 contextInitialized