初始化过程
- 通过SpringServletContainerInitializer来负责对容器启动时的相关组件进行初始化。
- 到底要初始化哪些组件则是通过Servlet规范中所提供的注解HandlesTypes来指定的。
- 在SpringServletContainerInitializer中,其HandlesTypes注解则明确指定为WebApplicationInitializer.class类型。
- 在SpringServletContainerInitializer的onStartup方法中,则主要是完成了一些验证和组件装配的工作。
- 在SpringServletContainerInitializer的onStartup方法中,由于某些容器并未遵循Servlet规范,导致虽然明确指定了HandlesTypes注解的类型为WebApplicationInitializer.class,但还是坑你会存在将一些非法类型传递过来的情况,所以,该方法还对传递过来的具体类型进行了细致的判断,只有符合条件的类型才会被纳入到
List<WebApplicationInitializer>
集合中。 - 当以上判断完成之后,
List<WebApplicationInitializer>
就是接下来需要进行初始化的组件了。 - 最后通过遍历
List<WebApplicationInitializer>
列表,取出其中的每一个WebApplicationInitializer对象,调用这些对象的onStartup方法,完成组件的启动初始化工作。
具体的内容
-
ServletContainerInitializer
/** * ServletContainerInitializers (SCIs) are registered via an entry in the * file META-INF/services/javax.servlet.ServletContainerInitializer that must be * included in the JAR file that contains the SCI implementation. * <p> * SCI processing is performed regardless of the setting of metadata-complete. * SCI processing can be controlled per JAR file via fragment ordering. If * absolute ordering is defined, then only the JARs included in the ordering * will be processed for SCIs. To disable SCI processing completely, an empty * absolute ordering may be defined. * <p> * SCIs register an interest in annotations (class, method or field) and/or * types via the {@link javax.servlet.annotation.HandlesTypes} annotation which * is added to the class. * * @since Servlet 3.0 */ public interface ServletContainerInitializer { /** * Receives notification during startup of a web application of the classes * within the web application that matched the criteria defined via the * {@link javax.servlet.annotation.HandlesTypes} annotation. * * @param c The (possibly null) set of classes that met the specified * criteria * @param ctx The ServletContext of the web application in which the * classes were discovered * * @throws ServletException If an error occurs */ void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException; }
- 从Servlet3.0开始,不强制使用
web.xml
文件作为应用容器启动的描述符文件。 -
ServletContainerInitializer
的onStartup
方法会在容器启动的时候被调用,以此完成一些初始化的动作 - 这是一个SPI的接口,在ServiceLoader(JVM类加载器相关知识)规范中定义配置文件位于
META-INF/services
文件夹下边,文件名为javax.servlet.ServletContainerInitializer
里边的定义为该接口的实现类,以此完成应用容器初始化后的调用,在Spring-web中该文件内容为org.springframework.web.SpringServletContainerInitializer
- 从Servlet3.0开始,不强制使用
-
SpringServletContainerInitializer
/** * Servlet 3.0 {@link ServletContainerInitializer} designed to support code-based * configuration of the servlet container using Spring's {@link WebApplicationInitializer} * SPI as opposed to (or possibly in combination with) the traditional * {@code web.xml}-based approach. * * <h2>Mechanism of Operation</h2> * This class will be loaded and instantiated and have its {@link #onStartup} * method invoked by any Servlet 3.0-compliant container during container startup assuming * that the {@code spring-web} module JAR is present on the classpath. This occurs through * the JAR Services API {@link ServiceLoader#load(Class)} method detecting the * {@code spring-web} module's {@code META-INF/services/javax.servlet.ServletContainerInitializer} * service provider configuration file. See the * <a href="https://download.oracle.com/javase/6/docs/technotes/guides/jar/jar.html#Service%20Provider"> * JAR Services API documentation</a> as well as section <em>8.2.4</em> of the Servlet 3.0 * Final Draft specification for complete details. * * <h3>In combination with {@code web.xml}</h3> * A web application can choose to limit the amount of classpath scanning the Servlet * container does at startup either through the {@code metadata-complete} attribute in * {@code web.xml}, which controls scanning for Servlet annotations or through an * {@code <absolute-ordering>} element also in {@code web.xml}, which controls which * web fragments (i.e. jars) are allowed to perform a {@code ServletContainerInitializer} * scan. When using this feature, the {@link SpringServletContainerInitializer} * can be enabled by adding "spring_web" to the list of named web fragments in * {@code web.xml} as follows: * * <pre class="code"> * <absolute-ordering> * <name>some_web_fragment</name> * <name>spring_web</name> * </absolute-ordering> * </pre> * * <h2>Relationship to Spring's {@code WebApplicationInitializer}</h2> * Spring's {@code WebApplicationInitializer} SPI consists of just one method: * {@link WebApplicationInitializer#onStartup(ServletContext)}. The signature is intentionally * quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}: * simply put, {@code SpringServletContainerInitializer} is responsible for instantiating * and delegating the {@code ServletContext} to any user-defined * {@code WebApplicationInitializer} implementations. It is then the responsibility of * each {@code WebApplicationInitializer} to do the actual work of initializing the * {@code ServletContext}. The exact process of delegation is described in detail in the * {@link #onStartup onStartup} documentation below. * * <h2>General Notes</h2> * In general, this class should be viewed as <em>supporting infrastructure</em> for * the more important and user-facing {@code WebApplicationInitializer} SPI. Taking * advantage of this container initializer is also completely <em>optional</em>: while * it is true that this initializer will be loaded and invoked under all Servlet 3.0+ * runtimes, it remains the user's choice whether to make any * {@code WebApplicationInitializer} implementations available on the classpath. If no * {@code WebApplicationInitializer} types are detected, this container initializer will * have no effect. * * <p>Note that use of this container initializer and of {@code WebApplicationInitializer} * is not in any way "tied" to Spring MVC other than the fact that the types are shipped * in the {@code spring-web} module JAR. Rather, they can be considered general-purpose * in their ability to facilitate convenient code-based configuration of the * {@code ServletContext}. In other words, any servlet, listener, or filter may be * registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific * components. * * <p>This class is neither designed for extension nor intended to be extended. * It should be considered an internal type, with {@code WebApplicationInitializer} * being the public-facing SPI. * * <h2>See Also</h2> * See {@link WebApplicationInitializer} Javadoc for examples and detailed usage * recommendations.<p> * * @author Chris Beams * @author Juergen Hoeller * @author Rossen Stoyanchev * @since 3.1 * @see #onStartup(Set, ServletContext) * @see WebApplicationInitializer */ @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { /** * Delegate the {@code ServletContext} to any {@link WebApplicationInitializer} * implementations present on the application classpath. * <p>Because this class declares @{@code HandlesTypes(WebApplicationInitializer.class)}, * Servlet 3.0+ containers will automatically scan the classpath for implementations * of Spring's {@code WebApplicationInitializer} interface and provide the set of all * such types to the {@code webAppInitializerClasses} parameter of this method. * <p>If no {@code WebApplicationInitializer} implementations are found on the classpath, * this method is effectively a no-op. An INFO-level log message will be issued notifying * the user that the {@code ServletContainerInitializer} has indeed been invoked but that * no {@code WebApplicationInitializer} implementations were found. * <p>Assuming that one or more {@code WebApplicationInitializer} types are detected, * they will be instantiated (and <em>sorted</em> if the @{@link * org.springframework.core.annotation.Order @Order} annotation is present or * the {@link org.springframework.core.Ordered Ordered} interface has been * implemented). Then the {@link WebApplicationInitializer#onStartup(ServletContext)} * method will be invoked on each instance, delegating the {@code ServletContext} such * that each instance may register and configure servlets such as Spring's * {@code DispatcherServlet}, listeners such as Spring's {@code ContextLoaderListener}, * or any other Servlet API componentry such as filters. * @param webAppInitializerClasses all implementations of * {@link WebApplicationInitializer} found on the application classpath * @param servletContext the servlet context to be initialized * @see WebApplicationInitializer#onStartup(ServletContext) * @see AnnotationAwareOrderComparator */ @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); } } }
-
SpringServletContainerInitializer
负责将ServletContext
的初始化委托给用户实现的WebApplicationInitializer
,所以ServletContext
的真正初始化是在WebApplicationInitializer
中完成的。 -
SpringServletContainerInitializer
作为WebApplicationInitializer
的基础设施而存在,但不是必须的。在servlet3.0的环境中会加载和调用SpringServletContainerInitializer
的onStartup
方法,用户可以选择任意的WebApplicationInitializer
的实现,如果找不到WebApplicationInitializer
,SpringServletContainerInitializer
将不做任何事。 - 通常来说
SpringServletContainerInitializer
是一个内部的类,如果要扩展建议实现WebApplicationInitializer
接口。 -
@HandlesTypes(WebApplicationInitializer.class)
是Servlet3.0的注解,作用是声明一个class的数组传递给ServletContainerInitializer
,扫描类路径下WebApplicationInitializer
接口的实现类,然后传递给public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException
方法作为第一个参数。
-
-
WebApplicationInitializer
public interface WebApplicationInitializer { /** * Configure the given {@link ServletContext} with any servlets, filters, listeners * context-params and attributes necessary for initializing this web application. See * examples {@linkplain WebApplicationInitializer above}. * @param servletContext the {@code ServletContext} to initialize * @throws ServletException if any call against the given {@code ServletContext} * throws a {@code ServletException} */ void onStartup(ServletContext servletContext) throws ServletException; }
总结
SpringServletContainerInitializer整个初始化过程中,其扮演的角色实际上是委托或是代理的角色,真正完成初始化工作的依旧是一个个的WebApplicationInitializer实现类。