上文中讲解了如何通过WebApplicationInitializer取代Web.xml进行spring容器的启动,WebApplicationInitializer是一个接口,通过实现WebApplicationInitializer,在其中可以添加servlet,listener等。在Web容器启动的时候,spring-web会通过SPI机制,加载这个接口的实现类,从而起到web.xml相同的作用。下面就看一下这个接口的详细内容:
public interface WebApplicationInitializer { void onStartup(ServletContext servletContext) throws ServletException; }
WebApplicationInitializer只有一个方法,比较简单,看不出什么头绪。在WebApplicationInitializer同级别有个SpringServletContainerInitializer类,我们来看下这个类的代码:
package org.springframework.web; @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); 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) 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; } AnnotationAwareOrderComparator.sort(initializers); servletContext.log("Spring WebApplicationInitializers detected on classpath: " + initializers); for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
SpringServletContainerInitializer这个类里也有个onStartup方法,看一下它的逻辑。首先判断webAppInitializerClasses这个set是否有值,如果有值,就找出里边不是接口、不是抽象类、实现了WebApplicationInitializer接口的类,保存到一个LinkedList中。
如果最终这个list为空,就返回空。如果最终list不为空,则按照一定顺序排序,将这些类实例化,调用其中的onStartup方法。
一旦我们实现了WebApplicationInitializer这个接口,spring-web就能扫描到WebApplicationInitializer的实现类,调用其中的onStartup方法,实现加载spring配置文件的目的。
但是,在web容器启动的时候,SpringServletContainerInitializer这个类又是怎么随着容器启动被调用呢?我们看一下SpringServletContainerInitializer这个类,它实现了ServletContainerInitializer接口,我们看一下ServletContainerInitializer接口代码:
public interface ServletContainerInitializer { public abstract void onStartup(Set set, ServletContext servletcontext) throws ServletException; }
ServletContainerInitializer这个类,是javax.servlet-api包下的类。
SpringServletContainerInitializer类中解释到,它实现了ServletContainerInitializer,通过SPI机制,当web容器启动时候,会到spring-web包的MATE-INF/services文件夹下扫描配置文件,在MATE-INF/services下有一个配置文件,文件名为ServletContainerInitializer接口全路径,文件内容为ServletContainerInitializer实现类的全路径,web容器扫描到该配置文件后,会将实现类实例化,我们看一下MATE-INF/services文件夹:
这样,web容器启动时,通过SPI机制,找到ServletContainerInitializer接口的实现类SpringServletContainerInitializer,将SpringServletContainerInitializer实例化,调用SpringServletContainerInitializer的onStartup方法,SpringServletContainerInitializer通过@HandlesTypes将WebApplicationInitializer注入进来,并调用WebApplicationInitializer的onStartup方法。至此,通过实现WebApplicationInitializer接口的onStartup方法,实现spring随着容器启动而被初始化。