【1】​​SpringServletContainerInitializer​

ServletContainerInitializer 该篇博文说明了ServletContainerInitializer是什么以及如何在项目中使用。

SpringMVC同样实现了该功能。

web容器在启动的时候,会扫描每个jar包下的​​META-INF/services/javax.servlet.ServletContainerInitializer​​,这里查看spring-web-4.3.11.RELEASE.jar包下的该文件。

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_SpringMVC


其源码如下:

/**
* 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.
* //简单地说,Servlet3.0(ServletContainerInitializer)就是倾向于使用“代码配置”方式完成web-xml的功能。
*
* //运行机制
* <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.
* //假设Spring Web}模块jar存在于类路径上,在容器(支持Servlet3.0)启动的时候,
* //将会加载并初始化ServletContainerInitializer,然后调用其onStartup方法。
*
* 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.
//ServiceLoader#load(Class)将会检测spring-web模块下的META-
* INF/services/javax.servlet.ServletContainerInitializer配置文件
*
* See the <a href="http://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.
*
// 与web.xml结合
* <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">
* {@code
* <absolute-ordering>
* <name>some_web_fragment</name>
* <name>spring_web</name>
* </absolute-ordering>
* }</pre>
*
和 Spring's {@code WebApplicationInitializer}之间的关系
* <h2>Relationship to Spring's {@code WebApplicationInitializer}</h2>
* Spring's {@code WebApplicationInitializer} SPI consists of just one method:
* {@link WebApplicationInitializer#onStartup(ServletContext)}.
* //Spring的WebApplicationInitializer只包含一个方法--onStartup
*
* //这个特征与ServletContainerInitializer.onStartup非常相似:
* The signature is intentionally quite similar to {@link ServletContainerInitializer#onStartup(Set, ServletContext)}:
* //简单地放入,SpringServletContainerInitializer将会负责初始化并委派ServletContext
* //给任何用户定义的WebApplicationInitializer实现。
* 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}.
* //然后每个WebApplicationInitializer负责初始化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.
* //一般而言,对于那些更重要的、面向用户的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.
//利用initializer 也是完全的可选的:虽然确实这个初始化器将在所有Servlet 3.0+运行时下加载和调用,
// 但是用户仍然可以选择是否实现WebApplicationInitializer。
//如果没有检测到{@代码WebApple初始化器}类型,则此容器初始化器将不起作用。

* <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.
* 注意,这个容器初始化器和 WebApplicationInitializer的使用
* 除了在code spring-web模块JAR中提供类型之外,
* 并没有以任何方式“绑定”到Spring MVC。
*
* Rather, they can be considered general-purpose
* in their ability to facilitate convenient code-based configuration of the
* {@code ServletContext}.
* //相反,它们在促进ServletContext的“基于代码”的配置能力上被认为是通用的。
* In other words, any servlet, listener, or filter may be
* registered within a {@code WebApplicationInitializer}, not just Spring MVC-specific
* components.
* //换句话说,任何Servlet、Listener和Filter都可以使用WebApplicationInitializer进行注册,不仅仅是SpringMVC特定组件。
*
* <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.
* 这个类既不为扩展而设计,也不打算扩展。
* 它应该被认为是内部类型,WebApplicationInitializer是面向公众的SPI。
*
* <h2>See Also</h2>
* See {@link WebApplicationInitializer} Javadoc for examples and detailed usage
* recommendations.<p>

* @since 3.1
* @see #onStartup(Set, ServletContext)
* @see WebApplicationInitializer
*/
// @HandlesTypes将会加载感兴趣的类型
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
//SpringServletContainerInitializer 实现了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(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 {
//实例化每个WebApplicationInitializer并添加到initializers中
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;
}

servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
// 将拿到的所有的initializer依次遍历调用其onStartup方法!
initializer.onStartup(servletContext);
}
}

}

主要意思:spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件,并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)。


WebApplicationInitializer 接口源码如下:

/**
* Interface to be implemented in Servlet 3.0+ environments in order to configure the
* {@link ServletContext} programmatically -- as opposed to (or possibly in conjunction
* with) the traditional {@code web.xml}-based approach.
*
* <p>Implementations of this SPI will be detected automatically by {@link
* SpringServletContainerInitializer}, which itself is bootstrapped automatically
* by any Servlet 3.0 container. See {@linkplain SpringServletContainerInitializer its
* Javadoc} for details on this bootstrapping mechanism.
*
* <h2>Example</h2>
* <h3>The traditional, XML-based approach</h3>
* Most Spring users building a web application will need to register Spring's {@code
* DispatcherServlet}. For reference, in WEB-INF/web.xml, this would typically be done as
* follows:
* <pre class="code">
* {@code
* <servlet>
* <servlet-name>dispatcher</servlet-name>
* <servlet-class>
* org.springframework.web.servlet.DispatcherServlet
* </servlet-class>
* <init-param>
* <param-name>contextConfigLocation</param-name>
* <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value>
* </init-param>
* <load-on-startup>1</load-on-startup>
* </servlet>
*
* <servlet-mapping>
* <servlet-name>dispatcher</servlet-name>
* <url-pattern>/</url-pattern>
* </servlet-mapping>}</pre>
*
* <h3>The code-based approach with {@code WebApplicationInitializer}</h3>
* Here is the equivalent {@code DispatcherServlet} registration logic,
* {@code WebApplicationInitializer}-style:
* <pre class="code">
* public class MyWebAppInitializer implements WebApplicationInitializer {
*
* &#064;Override
* public void onStartup(ServletContext container) {
* XmlWebApplicationContext appContext = new XmlWebApplicationContext();
* appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
*
* ServletRegistration.Dynamic dispatcher =
* container.addServlet("dispatcher", new DispatcherServlet(appContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
*
* }</pre>
*
* As an alternative to the above, you can also extend from {@link
* org.springframework.web.servlet.support.AbstractDispatcherServletInitializer}.
*
* As you can see, thanks to Servlet 3.0's new {@link ServletContext#addServlet} method
* we're actually registering an <em>instance</em> of the {@code DispatcherServlet}, and
* this means that the {@code DispatcherServlet} can now be treated like any other object
* -- receiving constructor injection of its application context in this case.
*
* <p>This style is both simpler and more concise. There is no concern for dealing with
* init-params, etc, just normal JavaBean-style properties and constructor arguments. You
* are free to create and work with your Spring application contexts as necessary before
* injecting them into the {@code DispatcherServlet}.
*
* <p>Most major Spring Web components have been updated to support this style of
* registration. You'll find that {@code DispatcherServlet}, {@code FrameworkServlet},
* {@code ContextLoaderListener} and {@code DelegatingFilterProxy} all now support
* constructor arguments. Even if a component (e.g. non-Spring, other third party) has not
* been specifically updated for use within {@code WebApplicationInitializers}, they still
* may be used in any case. The Servlet 3.0 {@code ServletContext} API allows for setting
* init-params, context-params, etc programmatically.
*
* <h2>A 100% code-based approach to configuration</h2>
* In the example above, {@code WEB-INF/web.xml} was successfully replaced with code in
* the form of a {@code WebApplicationInitializer}, but the actual
* {@code dispatcher-config.xml} Spring configuration remained XML-based.
* {@code WebApplicationInitializer} is a perfect fit for use with Spring's code-based
* {@code @Configuration} classes. See @{@link
* org.springframework.context.annotation.Configuration Configuration} Javadoc for
* complete details, but the following example demonstrates refactoring to use Spring's
* {@link org.springframework.web.context.support.AnnotationConfigWebApplicationContext
* AnnotationConfigWebApplicationContext} in lieu of {@code XmlWebApplicationContext}, and
* user-defined {@code @Configuration} classes {@code AppConfig} and
* {@code DispatcherConfig} instead of Spring XML files. This example also goes a bit
* beyond those above to demonstrate typical configuration of the 'root' application
* context and registration of the {@code ContextLoaderListener}:
* <pre class="code">
* public class MyWebAppInitializer implements WebApplicationInitializer {
*
* &#064;Override
* public void onStartup(ServletContext container) {
* // Create the 'root' Spring application context
* AnnotationConfigWebApplicationContext rootContext =
* new AnnotationConfigWebApplicationContext();
* rootContext.register(AppConfig.class);
*
* // Manage the lifecycle of the root application context
* container.addListener(new ContextLoaderListener(rootContext));
*
* // Create the dispatcher servlet's Spring application context
* AnnotationConfigWebApplicationContext dispatcherContext =
* new AnnotationConfigWebApplicationContext();
* dispatcherContext.register(DispatcherConfig.class);
*
* // Register and map the dispatcher servlet
* ServletRegistration.Dynamic dispatcher =
* container.addServlet("dispatcher", new DispatcherServlet(dispatcherContext));
* dispatcher.setLoadOnStartup(1);
* dispatcher.addMapping("/");
* }
*
* }</pre>
*
* As an alternative to the above, you can also extend from {@link
* org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}.
*
* Remember that {@code WebApplicationInitializer} implementations are <em>detected
* automatically</em> -- so you are free to package them within your application as you
* see fit.
*
* <h2>Ordering {@code WebApplicationInitializer} execution</h2>
* {@code WebApplicationInitializer} implementations may optionally be annotated at the
* class level with Spring's @{@link org.springframework.core.annotation.Order Order}
* annotation or may implement Spring's {@link org.springframework.core.Ordered Ordered}
* interface. If so, the initializers will be ordered prior to invocation. This provides
* a mechanism for users to ensure the order in which servlet container initialization
* occurs. Use of this feature is expected to be rare, as typical applications will likely
* centralize all container initialization within a single {@code WebApplicationInitializer}.
*
* <h2>Caveats</h2>
*
* <h3>web.xml versioning</h3>
* <p>{@code WEB-INF/web.xml} and {@code WebApplicationInitializer} use are not mutually
* exclusive; for example, web.xml can register one servlet, and a {@code
* WebApplicationInitializer} can register another. An initializer can even
* <em>modify</em> registrations performed in {@code web.xml} through methods such as
* {@link ServletContext#getServletRegistration(String)}. <strong>However, if
* {@code WEB-INF/web.xml} is present in the application, its {@code version} attribute
* must be set to "3.0" or greater, otherwise {@code ServletContainerInitializer}
* bootstrapping will be ignored by the servlet container.</strong>
*
* <h3>Mapping to '/' under Tomcat</h3>
* <p>Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions
* <= 7.0.14, this servlet mapping <em>cannot be overridden programmatically</em>.
* 7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested
* successfully under GlassFish 3.1.<p>
*
* @author Chris Beams
* @since 3.1
* @see SpringServletContainerInitializer
* @see org.springframework.web.context.AbstractContextLoaderInitializer
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
* @see org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
*/
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;

}

WebApplicationInitializer接口的抽象子类如下图:

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_SpringMVC_02


AbstractAnnotationConfigDispatcherServletInitializer源码如下:

/**
* Base class for {@link org.springframework.web.WebApplicationInitializer}
* implementations that register a
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* configured with annotated classes, e.g. Spring's
* {@link org.springframework.context.annotation.Configuration @Configuration} classes.
*
* <p>Concrete implementations are required to implement {@link #getRootConfigClasses()}
* and {@link #getServletConfigClasses()} as well as {@link #getServletMappings()}.
* //具体实现类需要实现getRootConfigClasses方法、getServletConfigClasses方法
* //以及getServletMappings方法。
*
* Further template and customization methods are provided by
* {@link AbstractDispatcherServletInitializer}.
* 进一步的模板和自定义方法由AbstractDispatcherServletInitializer
* //(AbstractAnnotationConfigDispatcherServletInitializer的父类)提供。
*
*
//这是使用基于Java的Spring配置的应用程序的首选方法。
* <p>This is the preferred approach for applications that use Java-based
* Spring configuration.

* @since 3.2
*/
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {

/**
* {@inheritDoc}
* <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},
* providing it the annotated classes returned by {@link #getRootConfigClasses()}.
* Returns {@code null} if {@link #getRootConfigClasses()} returns {@code null}.
*/
@Override
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}

/**
* {@inheritDoc}
* <p>This implementation creates an {@link AnnotationConfigWebApplicationContext},
* providing it the annotated classes returned by {@link #getServletConfigClasses()}.
*/
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}

/**
* Specify {@link org.springframework.context.annotation.Configuration @Configuration}
* and/or {@link org.springframework.stereotype.Component @Component} classes to be
* provided to the {@linkplain #createRootApplicationContext() root application context}.
* @return the configuration classes for the root application context, or {@code null}
* if creation and registration of a root context is not desired
*/
protected abstract Class<?>[] getRootConfigClasses();

/**
* Specify {@link org.springframework.context.annotation.Configuration @Configuration}
* and/or {@link org.springframework.stereotype.Component @Component} classes to be
* provided to the {@linkplain #createServletApplicationContext() dispatcher servlet
* application context}.
* @return the configuration classes for the dispatcher servlet application context or
* {@code null} if all configuration is specified through root config classes.
*/
protected abstract Class<?>[] getServletConfigClasses();

}

createRootApplicationContext和createServletApplicationContext分别使用rootConfig和servletConfig创建所谓的RootApplicationContext和ServletApplicationContext(其实都是AnnotationConfigWebApplicationContext ,只不过配置类不同)。

但是getRootConfigClasses和getServletConfigClasses是两个抽象方法,需要我们自己实现。


那么其父类AbstractDispatcherServletInitializer 干了什么呢?

AbstractDispatcherServletInitializer 源码如下:

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {

/**
* The default servlet name. Can be customized by overriding {@link #getServletName}.
*/
public static final String DEFAULT_SERVLET_NAME = "dispatcher";


//这里,子类最下面的onStartup方法。首先调用了父类的onStartup方法,
//然后开始注册Servlet和Filter!
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}

/**
* Register a {@link DispatcherServlet} against the given servlet context.
* <p>This method will create a {@code DispatcherServlet} with the name returned by
* {@link #getServletName()}, initializing it with the application context returned
* from {@link #createServletApplicationContext()}, and mapping it to the patterns
* returned from {@link #getServletMappings()}.
* <p>Further customization can be achieved by overriding {@link
* #customizeRegistration(ServletRegistration.Dynamic)} or
* {@link #createDispatcherServlet(WebApplicationContext)}.
* @param servletContext the context to register the servlet against
*/
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");

//创建ServletApplicationContext--实际为AnnotationConfigWebApplicationContext
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());

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

//注册并配置dispatcherServlet
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);
}

/**
* Return the name under which the {@link DispatcherServlet} will be registered.
* Defaults to {@link #DEFAULT_SERVLET_NAME}.
* @see #registerDispatcherServlet(ServletContext)
*/
protected String getServletName() {
return DEFAULT_SERVLET_NAME;
}

/**
* Create a servlet application context to be provided to the {@code DispatcherServlet}.
* <p>The returned context is delegated to Spring's
* {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such,
* it typically contains controllers, view resolvers, locale resolvers, and other
* web-related beans.
* @see #registerDispatcherServlet(ServletContext)
*/
// 抽象方法,AbstractAnnotationConfigDispatcherServletInitializer进行了实现。
protected abstract WebApplicationContext createServletApplicationContext();

/**
* Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived
* dispatcher) with the specified {@link WebApplicationContext}.
* <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3.
* Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof.
*/
protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) {
return new DispatcherServlet(servletAppContext);
}

/**
* Specify application context initializers to be applied to the servlet-specific
* application context that the {@code DispatcherServlet} is being created with.
* @since 4.2
* @see #createServletApplicationContext()
* @see DispatcherServlet#setContextInitializers
* @see #getRootApplicationContextInitializers()
*/
@Nullable
protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
return null;
}

/**
* Specify the servlet mapping(s) for the {@code DispatcherServlet} &mdash;
* for example {@code "/"}, {@code "/app"}, etc.
* @see #registerDispatcherServlet(ServletContext)
*/
// 这里抽象方法,没有被WebApplicationInitializer任意子类实现,需要用户实现。
protected abstract String[] getServletMappings();

/**
* Specify filters to add and map to the {@code DispatcherServlet}.
* @return an array of filters or {@code null}
* @see #registerServletFilter(ServletContext, Filter)
*/
@Nullable
protected Filter[] getServletFilters() {
return null;
}

/**
* Add the given filter to the ServletContext and map it to the
* {@code DispatcherServlet} as follows:
* <ul>
* <li>a default filter name is chosen based on its concrete type
* <li>the {@code asyncSupported} flag is set depending on the
* return value of {@link #isAsyncSupported() asyncSupported}
* <li>a filter mapping is created with dispatcher types {@code REQUEST},
* {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending
* on the return value of {@link #isAsyncSupported() asyncSupported}
* </ul>
* <p>If the above defaults are not suitable or insufficient, override this
* method and register filters directly with the {@code ServletContext}.
* @param servletContext the servlet context to register filters with
* @param filter the filter to be registered
* @return the filter registration
*/
//这里注册Filter!!
protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
String filterName = Conventions.getVariableName(filter);
Dynamic registration = servletContext.addFilter(filterName, filter);

if (registration == null) {
int counter = 0;
while (registration == null) {
if (counter == 100) {
throw new IllegalStateException("Failed to register filter with name '" + filterName + "'. " +
"Check if there is another filter registered under the same name.");
}
registration = servletContext.addFilter(filterName + "#" + counter, filter);
counter++;
}
}

registration.setAsyncSupported(isAsyncSupported());
registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName());
return registration;
}

private EnumSet<DispatcherType> getDispatcherTypes() {
return (isAsyncSupported() ?
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) :
EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE));
}

/**
* A single place to control the {@code asyncSupported} flag for the
* {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}.
* <p>The default value is "true".
*/
protected boolean isAsyncSupported() {
return true;
}

/**
* Optionally perform further registration customization once
* {@link #registerDispatcherServlet(ServletContext)} has completed.
* @param registration the {@code DispatcherServlet} registration to be customized
* @see #registerDispatcherServlet(ServletContext)
*/
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
}

}

AbstractDispatcherServletInitializer注册了dispatcherServlet和Filter,并调用了父类的onStartup方法。


继续看AbstractDispatcherServletInitializer的父类AbstractContextLoaderInitializer!

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

/**
* Register a {@link ContextLoaderListener} against the given servlet context. The
* {@code ContextLoaderListener} is initialized with the application context returned
* from the {@link #createRootApplicationContext()} template method.
* @param servletContext the servlet context to register the listener against
*/
protected void registerContextLoaderListener(ServletContext servletContext) {
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
//将ContextLoaderListener放进了servletContext里面
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");
}
}

/**
* Create the "<strong>root</strong>" application context to be provided to the
* {@code ContextLoaderListener}.
* <p>The returned context is delegated to
* {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will
* be established as the parent context for any {@code DispatcherServlet} application
* contexts. As such, it typically contains middle-tier services, data sources, etc.
* @return the root application context, or {@code null} if a root context is not
* desired
* @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
*/
// 抽象方法,AbstractAnnotationConfigDispatcherServletInitializer进行了实现。
@Nullable
protected abstract WebApplicationContext createRootApplicationContext();

/**
* Specify application context initializers to be applied to the root application
* context that the {@code ContextLoaderListener} is being created with.
* @since 4.2
* @see #createRootApplicationContext()
* @see ContextLoaderListener#setContextInitializers
*/
@Nullable
protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
return null;
}

}

至此面向用户的WebApplicationInitializer的几个实现类都分析完毕!

通过源码可知我们可以使用注解方式来初始化SpringMVC-----继承​​AbstractAnnotationConfigDispatcherServletInitializer​​并实现getRootConfigClasses、getServletConfigClasses和getServletMappings方法。


【2】MyWebAppInitializer

MyWebAppInitializer类如下:

  • 对比web.xml功能
//web容器启动的时候创建对象;调用方法来初始化容器以前前端控制器
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

//获取根容器的配置类--Spring的配置文件--父容器;
@Override
protected Class<?>[] getRootConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{RootConfig.class};
}

//获取web容器的配置类--SpringMVC配置文件--子容器;
@Override
protected Class<?>[] getServletConfigClasses() {
// TODO Auto-generated method stub
return new Class<?>[]{AppConfig.class};
}

//获取DispatcherServlet的映射信息
// /:拦截所有请求(包括静态资源(xx.js,xx.png)),但是不包括*.jsp;
// /*:拦截所有请求;连*.jsp页面都拦截;jsp页面是tomcat的jsp引擎解析的;
@Override
protected String[] getServletMappings() {
// TODO Auto-generated method stub
return new String[]{"/"};
}

}

RootConfig类如下(applicationContext.xml):

//Spring的容器不扫描controller;父容器
@ComponentScan(value="com.web",excludeFilters={
@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
})
public class RootConfig {

}

AppConfig类如下(springmvc-servlet.xml):

//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan(value="com.web",includeFilters={
@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
public class AppConfig {

}

如上所示,使用代码配置方式模拟web.xml,applicationContext.xml和springmvc-servlet.xml的功能。

MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer,故而MyWebAppInitializer 是WebApplicationInitializer类型,在Tomcat启动的时候SpringServletContainerInitializer会将WebApplicationInitializer类型的类加载并进行实例化。

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_spring_03


【3】全注解实现SpringMVC

项目中使用SpringMVC时,常常会有一个xml文件进行配置,如何取代该xml呢?参考Spring官方文档:​​https://docs.spring.io/spring/docs/5.0.5.RELEASE/spring-framework-reference/web.html#mvc-config​

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_SpringMVC_04


拓展AppConfig如下:

//SpringMVC只扫描Controller;子容器
//useDefaultFilters=false 禁用默认的过滤规则;
@ComponentScan(value="com.web",includeFilters={
@Filter(type=FilterType.ANNOTATION,classes={Controller.class})
},useDefaultFilters=false)
@EnableWebMvc
public class AppConfig extends WebMvcConfigurerAdapter {

//定配置

//视图解析器
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// TODO Auto-generated method stub
//默认所有的页面都从 /WEB-INF/ xxx .jsp
//registry.jsp();
registry.jsp("/WEB-INF/views/", ".jsp");
}

//静态资源访问
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
// TODO Auto-generated method stub
configurer.enable();
}

//拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// TODO Auto-generated method stub
//super.addInterceptors(registry);
registry.addInterceptor(new MyFirstInterceptor()).addPathPatterns("/**");
}

}

@EnableWebMvc:开启SpringMVC定制配置功能相当于如下标签:

<mvc:annotation-driven/>

如下图所示,SpringMVC相关的定制配置可以通过实现WebMvcConfigurerAdapter接口,这里我们继承WebMvcConfigurerAdapter。

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_xml_05

public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {

//...
}

WebMvcConfigurerAdapter 方法如下图:

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_SpringMVC_06


但是,SpringBoot下,你很少需要使用@EnableWebMvc全面接管SpringMVC的配置。

具体参考博文:SpringBoot - SpringMVC的默认配置与修改。