本系列文章基于以下版本。
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) { 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 { 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 { fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null ); ... 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 ; } } 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
。