【1】SpringServletContainerInitializer
ServletContainerInitializer 该篇说明了ServletContainerInitializer是什么以及如何在项目中使用。
SpringMVC同样实现了该功能。
web容器在启动的时候,会扫描每个jar包下的META-
INF/services/javax.servlet.ServletContainerInitializer
,这里查看spring-web-4.3.11.RELEASE.jar包下的该文件。
其源码如下:
/** * 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 { * * @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 { * * @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接口的抽象子类如下图:
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} — * 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类型的类加载并进行实例化。
【3】全注解实现SpringMVC
项目中使用SpringMVC时,常常会有一个xml文件进行配置,如何取代该xml呢?
拓展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。
public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer { //... }
WebMvcConfigurerAdapter 方法如下图:
但是,SpringBoot下,你很少需要使用@EnableWebMvc全面接管SpringMVC的配置。