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

【1】SpringServletContainerInitializer

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

SpringMVC同样实现了该功能。

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

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_第1张图片


其源码如下:

/**
 * 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的功能。
 * 
 * //运行机制
 * 

Mechanism of Operation

* 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 * JAR Services API documentation as well as section 8.2.4 of the Servlet 3.0 * Final Draft specification for complete details. * // 与web.xml结合 *

In combination with {@code web.xml}

* 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 } 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: * *
 * {@code
 * 
 *   some_web_fragment
 *   spring_web
 * 
 * }
* 和 Spring's {@code WebApplicationInitializer}之间的关系 *

Relationship to Spring's {@code WebApplicationInitializer}

* 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. * //下面的文档详细描述了委派过程 * //基本注释 *

General Notes

* In general, this class should be viewed as supporting infrastructure for * the more important and user-facing {@code WebApplicationInitializer} SPI. * //一般而言,对于那些更重要的、面向用户的SPI来讲,该类应该被当做“基础设施”。 * Taking * advantage of this container initializer is also completely optional: 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初始化器}类型,则此容器初始化器将不起作用。 *

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特定组件。 * *

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。 * *

See Also

* See {@link WebApplicationInitializer} Javadoc for examples and detailed usage * recommendations.

* @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. *

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. *

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. *

Assuming that one or more {@code WebApplicationInitializer} types are detected, * they will be instantiated (and sorted 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> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List 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 { //实例化每个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.
 *
 * 

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. * *

Example

*

The traditional, XML-based approach

* 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: *
 * {@code
 * 
 *   dispatcher
 *   
 *     org.springframework.web.servlet.DispatcherServlet
 *   
 *   
 *     contextConfigLocation
 *     /WEB-INF/spring/dispatcher-config.xml
 *   
 *   1
 * 
 *
 * 
 *   dispatcher
 *   /
 * }
* *

The code-based approach with {@code WebApplicationInitializer}

* Here is the equivalent {@code DispatcherServlet} registration logic, * {@code WebApplicationInitializer}-style: *
 * 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("/");
 *    }
 *
 * }
* * 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 instance 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. * *

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}. * *

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. * *

A 100% code-based approach to configuration

* 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}: *
 * 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("/");
 *    }
 *
 * }
* * As an alternative to the above, you can also extend from {@link * org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer}. * * Remember that {@code WebApplicationInitializer} implementations are detected * automatically -- so you are free to package them within your application as you * see fit. * *

Ordering {@code WebApplicationInitializer} execution

* {@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}. * *

Caveats

* *

web.xml versioning

*

{@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 * modify registrations performed in {@code web.xml} through methods such as * {@link ServletContext#getServletRegistration(String)}. 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. * *

Mapping to '/' under Tomcat

*

Apache Tomcat maps its internal {@code DefaultServlet} to "/", and on Tomcat versions * <= 7.0.14, this servlet mapping cannot be overridden programmatically. * 7.0.15 fixes this issue. Overriding the "/" servlet mapping has also been tested * successfully under GlassFish 3.1.

* * @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.
 *
 * 

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配置的应用程序的首选方法。 *

This is the preferred approach for applications that use Java-based * Spring configuration. * @since 3.2 */ public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer { /** * {@inheritDoc} *

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} *

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.
	 * 

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()}. *

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}. *

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}. *

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: *

    *
  • a default filter name is chosen based on its concrete type *
  • the {@code asyncSupported} flag is set depending on the * return value of {@link #isAsyncSupported() asyncSupported} *
  • 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} *
*

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 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()}. *

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 "root" application context to be provided to the
	 * {@code ContextLoaderListener}.
	 * 

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注解配置项目_第2张图片


【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注解配置项目_第3张图片


拓展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定制配置功能相当于如下标签:


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

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_第4张图片

public abstract class WebMvcConfigurerAdapter implements WebMvcConfigurer {

	//...
}

WebMvcConfigurerAdapter 方法如下图:

SpringMVC与Servlet3.0整合 - ServletContainerInitializer注解配置项目_第5张图片


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

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

你可能感兴趣的:(#,SpringMVC)