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

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

【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 {
 *
 *    &#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
 * &lt;= 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} &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类型的类加载并进行实例化。


【3】全注解实现SpringMVC

项目中使用SpringMVC时,常常会有一个xml文件进行配置,如何取代该xml呢?

20180420162131620.png



拓展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的配置。

目录
相关文章
|
7月前
|
Java 应用服务中间件 程序员
如何利用Idea创建一个Servlet项目(新手向)(下)
如何利用Idea创建一个Servlet项目(新手向)(下)
170 0
|
7月前
|
Java 应用服务中间件 API
如何利用Idea创建一个Servlet项目(新手向)(上)
如何利用Idea创建一个Servlet项目(新手向)
410 0
|
2天前
|
前端开发 Java 数据安全/隐私保护
【SpringMVC】用户登录器项目,加法计算器项目的实现
用户登录器的实现,加法计算器的实现
|
2月前
|
设计模式 前端开发 Java
Spring MVC——项目创建和建立请求连接
MVC是一种软件架构设计模式,将应用分为模型、视图和控制器三部分。Spring MVC是基于MVC模式的Web框架,通过`@RequestMapping`等注解实现URL路由映射,支持GET和POST请求,并可传递参数。创建Spring MVC项目与Spring Boot类似,使用`@RestController`注解标记控制器类。
51 1
Spring MVC——项目创建和建立请求连接
|
6月前
|
Java 应用服务中间件 Maven
IDEA创建一个Servlet项目(tomcat10)
IDEA创建一个Servlet项目(tomcat10)
318 1
|
2月前
|
前端开发 Java 应用服务中间件
【Spring】Spring MVC的项目准备和连接建立
【Spring】Spring MVC的项目准备和连接建立
65 2
|
4月前
|
前端开发 JavaScript
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
这篇文章介绍了使用AJAX技术将前端页面中表单接收的多个参数快速便捷地传输到后端servlet的方法,并通过示例代码展示了前端JavaScript中的AJAX调用和后端servlet的接收处理。
这篇文章介绍了如何使用form表单结合Bootstrap格式将前端数据通过action属性提交到后端的servlet,包括前端表单的创建、数据的一级和二级验证,以及后端servlet的注解和参数获取。
|
7月前
|
XML Java 应用服务中间件
Tomcat_servlet部署、编译、配置、打包
Tomcat_servlet部署、编译、配置、打包
110 0
|
4月前
|
存储 前端开发 Java
servlet过滤器--使用过滤器统计网站访问人数的计数(注解形式)
该文章展示了如何使用Servlet过滤器(Filter)通过注解方式创建一个网站访问人数统计功能,通过`@WebFilter`注解定义过滤器及其URL模式,并在`doFilter`方法中实现计数逻辑,将访问次数存储在`ServletContext`中,最后在JSP页面展示访问人数。
servlet过滤器--使用过滤器统计网站访问人数的计数(注解形式)
|
4月前
|
前端开发 Java 开发工具
servlet技术--使用注解模拟用户登录实现页面跳转
该文章介绍了Servlet技术的使用,通过注解方式开发Servlet来模拟用户登录功能,并在登录成功后实现页面跳转,展示用户的用户名和密码。
servlet技术--使用注解模拟用户登录实现页面跳转