SpringMVC啟動(dòng)原理

本文從API角度入手,帶你了解SpringMVC啟動(dòng)的原理。

ServletContainerInitializer

在web容器啟動(dòng)時(shí)為提供給第三方組件機(jī)會(huì)做一些初始化的工作,例如注冊(cè)servlet或者filtes等,servlet規(guī)范中通過(guò)ServletContainerInitializer實(shí)現(xiàn)此功能。
每個(gè)框架要使用ServletContainerInitializer就必須在對(duì)應(yīng)的jar包的META-INF/services 目錄創(chuàng)建一個(gè)名為javax.servlet.ServletContainerInitializer的文件,文件內(nèi)容指定具體的ServletContainerInitializer實(shí)現(xiàn)類,
那么,當(dāng)web容器啟動(dòng)時(shí)就會(huì)運(yùn)行這個(gè)初始化器做一些組件內(nèi)的初始化工作。

一般伴隨著ServletContainerInitializer一起使用的還有HandlesTypes注解,通過(guò)HandlesTypes可以將感興趣的一些類注入到ServletContainerInitializerdeonStartup方法作為參數(shù)傳入。

Tomcat容器的ServletContainerInitializer機(jī)制的實(shí)現(xiàn),主要交由Context容器ContextConfig監(jiān)聽器共同實(shí)現(xiàn),
ContextConfig監(jiān)聽器負(fù)責(zé)在容器啟動(dòng)時(shí)讀取每個(gè)web應(yīng)用的WEB-INF/lib目錄下包含的jar包的META-INF/services/javax.servlet.ServletContainerInitializer,以及web根目錄下的META-INF/services/javax.servlet.ServletContainerInitializer
通過(guò)反射完成這些ServletContainerInitializer的實(shí)例化,然后再設(shè)置到Context容器中,最后Context容器啟動(dòng)時(shí)就會(huì)分別調(diào)用每個(gè)ServletContainerInitializeronStartup方法,并將感興趣的類作為參數(shù)傳入。

image.png

基本的實(shí)現(xiàn)機(jī)制如圖,首先通過(guò)ContextConfig監(jiān)聽器遍歷每個(gè)jar包或web根目錄的META-INF/services/javax.servlet.ServletContainerInitializer文件,根據(jù)讀到的類路徑實(shí)例化每個(gè)ServletContainerInitializer;然后再分別將實(shí)例化好的ServletContainerInitializer設(shè)置進(jìn)Context容器中;最后Context容器啟動(dòng)時(shí)分別調(diào)用所有ServletContainerInitializer對(duì)象的onStartup方法。

假如讀出來(lái)的內(nèi)容為com.seaboat.mytomcat.CustomServletContainerInitializer,則通過(guò)反射實(shí)例化一個(gè)CustomServletContainerInitializer對(duì)象,這里涉及到一個(gè)@HandlesTypes注解的處理,被它標(biāo)明的類需要作為參數(shù)值傳入到onStartup方法。

如下例子:

@HandlesTypes({ HttpServlet.class,Filter.class }) 
public class CustomServletContainerInitializer implements 
    ServletContainerInitializer { 
  public void onStartup(Set<Class<?>> classes, ServletContext servletContext) 
      throws ServletException {
      for(Class c : classes) 
         System.out.println(c.getName());
  } 
}

其中@HandlesTypes標(biāo)明的HttpServletFilter兩個(gè)class被注入到了onStartup方法。
所以這個(gè)注解也是需要在ContextConfig監(jiān)聽器中處理

前面已經(jīng)介紹了注解的實(shí)現(xiàn)原理,由于有了編譯器的協(xié)助,我們可以方便地通過(guò)ServletContainerInitializer的class對(duì)象中獲取到HandlesTypes對(duì)象,進(jìn)而再獲取到注解聲明的類數(shù)組,如

HandlesTypes ht =servletContainerInitializer.getClass().getAnnotation(HandlesTypes.class);
Class<?>[] types = ht.value();

即可獲取到HttpServletFilter的class對(duì)象數(shù)組,后面Context容器調(diào)用CustomServletContainerInitializer對(duì)象的onStartup方法時(shí)作為參數(shù)傳入。

至此,即完成了servlet規(guī)范的ServletContainerInitializer初始化器機(jī)制。

SpringServletContainerInitializer

上面提到了META-INF/services/javax.servlet.ServletContainerInitializer,在Springspring-web-4.3.0.RELEASE.jar Jar包中可以找到該文件,內(nèi)容如下:

org.springframework.web.SpringServletContainerInitializer

下面,我們就來(lái)詳細(xì)講解下SpringServletContainerInitializer


首先看下API中的描述:

Servlet 3.0 ServletContainerInitializer被設(shè)計(jì)為使用Spring的WebApplicationInitializer SPI來(lái)支持Servlet容器的基于代碼的配置,而不是傳統(tǒng)的基于web.xml的配置(也可能兩者結(jié)合使用)。

一、運(yùn)作機(jī)制

假設(shè)類路徑中存在spring-web模塊的JAR包,SpringServletContainerInitializer將被加載并實(shí)例化,并且在容器啟動(dòng)期間由Servlet 3.0容器調(diào)用onStartup方法。

這是通過(guò)JAR Services API ServiceLoader.load(Class)方法(檢測(cè)Spring-Web模塊的META-INF/services/javax.servlet.ServletContainerInitializer配置文件)實(shí)現(xiàn)的。

二、與web.xml結(jié)合使用

Web應(yīng)用程序可以選擇通過(guò)web.xml中的metadata-complete屬性(它控制掃描Servlet注解的行為)
或通過(guò)web.xml中的<absolute-ordering>元素(它控制哪些web fragments(i.e. jars)被允許執(zhí)行掃描ServletContainerInitializer)
來(lái)限制Servlet容器在啟動(dòng)時(shí)掃描的類路徑。

當(dāng)使用這個(gè)特性時(shí),可以通過(guò)添加"spring_web"到web.xml里的web fragments列表來(lái)啟用SpringServletContainerInitializer

如下所示:

<absolute-ordering>
    <name>some_web_fragment</name>
    <name>spring_web</name>
</absolute-ordering>

servlet3.X中的metadata-complete屬性
在Servlet3.X的web.xml中可以設(shè)置metadata-complete屬性,例如:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
                  http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
     version="3.1"
     metadata-complete="true">
</web-app>

如果設(shè)置metadata-complete="true",會(huì)在啟動(dòng)時(shí)不掃描注解(annotation)。如果不掃描注解的話,用注解進(jìn)行的配置就無(wú)法生效,例如:@WebServlet

三、與Spring的WebApplicationInitializer的關(guān)系

Spring的WebApplicationInitializer SPI僅由一個(gè)方法組成:WebApplicationInitializer.onStartup(ServletContext)。聲明與ServletContainerInitializer.onStartup(Set, ServletContext)非常相似:簡(jiǎn)單地說(shuō),SpringServletContainerInitializer負(fù)責(zé)將ServletContext實(shí)例化并委托給用戶定義的WebApplicationInitializer實(shí)現(xiàn)。然后每個(gè)WebApplicationInitializer負(fù)責(zé)完成初始化ServletContext的實(shí)際工作。下面的onStartup文檔中詳細(xì)介紹了委托的具體過(guò)程。

四、注意事項(xiàng)

一般來(lái)說(shuō),這個(gè)類應(yīng)該被視為WebApplicationInitializer SPI的支持。利用這個(gè)容器初始化器也是完全可選的:雖然這個(gè)初始化器在所有的Servlet 3.0+運(yùn)行環(huán)境下被加載和調(diào)用,但用戶可以選擇是否提供WebApplicationInitializer實(shí)現(xiàn)。如果未檢測(cè)到WebApplicationInitializer類型,則此SpringServletContainerInitializer將不起作用。

請(qǐng)注意,除了這些類型是在spring-web模塊JAR中提供的,使用這個(gè)SpringServletContainerInitializerWebApplicationInitializer與Spring MVC沒(méi)有任何“捆綁”。相反,它們可以被認(rèn)為是通用的,以便于簡(jiǎn)化ServletContext基于代碼的配置。換句話說(shuō),任何servlet, listener, 或者filter都可以在WebApplicationInitializer中注冊(cè),而不僅僅是Spring MVC特定的組件。

SpringServletContainerInitializer既不是為擴(kuò)展而設(shè)計(jì)的。它應(yīng)該被認(rèn)為是一個(gè)內(nèi)部類型,WebApplicationInitializer是面向用戶的SPI。


好啦,現(xiàn)在對(duì)SpringServletContainerInitializer有了一個(gè)比較透徹的了解,下面我們來(lái)看一下唯一的onStartup方法。

ServletContext委托給類路徑中的WebApplicationInitializer實(shí)現(xiàn)。
因?yàn)檫@個(gè)類聲明了@HandlesTypes(WebApplicationInitializer.class),所以
Servlet 3.0+容器會(huì)自動(dòng)掃描類路徑下Spring的WebApplicationInitializer接口的實(shí)現(xiàn),并將所有這些類型的集合提供給這個(gè)方法的webAppInitializerClasses參數(shù)。
如果在類路徑下找不到WebApplicationInitializer實(shí)現(xiàn),則此方法不會(huì)有任何操作。將發(fā)出INFO級(jí)別的日志消息,通知用戶ServletContainerInitializer確實(shí)已被調(diào)用,但沒(méi)有找到WebApplicationInitializer實(shí)現(xiàn)。
假設(shè)檢測(cè)到一個(gè)或多個(gè)WebApplicationInitializer類型,它們將被實(shí)例化(如果存在@Order注釋或?qū)崿F(xiàn)Ordered接口,則對(duì)其進(jìn)行排序)。然后,將調(diào)用每個(gè)實(shí)例WebApplicationInitializer.onStartup(ServletContext)方法,并委派ServletContext,以便每個(gè)實(shí)例都可以注冊(cè)和配置Servlet,例如Spring的DispatcherServlet,listeners(如Spring的ContextLoaderListener),或者其他Servlet API組件(如filters)。


下面是SpringServletContainerInitializer的源碼:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
            throws ServletException {

        // WebApplicationInitializer實(shí)現(xiàn)如果存在`@Order`注釋或?qū)崿F(xiàn)`Ordered`接口,則對(duì)其進(jìn)行排序,故這里使用LinkedList
        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...
                // 有時(shí)候,Servlet容器提供給我們的可能是無(wú)效的webAppInitializerClass
                if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
                        WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
                    try {
                        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(servletContext);
        }
    }
}

WebApplicationInitializer

下面我們來(lái)看下WebApplicationInitializerAPI文檔的相關(guān)介紹。

在Servlet 3.0+環(huán)境中實(shí)現(xiàn)該接口,以便以編程方式配置ServletContext,而不是以傳統(tǒng)的基于web.xml的方法。WebApplicationInitializer SPI的實(shí)現(xiàn)將被SpringServletContainerInitializer(它本身是由Servlet 3.0容器自動(dòng)引導(dǎo)的)自動(dòng)檢測(cè)到。

Example

基于XML的方式

大多數(shù)Spring用戶構(gòu)建Web應(yīng)用程序時(shí)需要注冊(cè)Spring的DispatcherServlet。作為參考,通常在WEB-INF/web.xml中按如下方式:

 <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>
基于代碼的方式

DispatcherServlet注冊(cè)邏輯與上述等效

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

作為上述的替代方法,您還可以繼承自org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
正如您所看到的,使用Servlet 3.0的ServletContext.addServlet方法,我們注冊(cè)了一個(gè)DispatcherServlet的實(shí)例。

這種風(fēng)格簡(jiǎn)單明了。不用關(guān)心處理init-params等,只是普通的JavaBean風(fēng)格的屬性和構(gòu)造函數(shù)參數(shù)。在將其注入到DispatcherServlet之前,您可以根據(jù)需要自由創(chuàng)建和使用Spring應(yīng)用程序上下文。

大多數(shù)Spring Web組件已經(jīng)更新,以支持這種注冊(cè)方式。你會(huì)發(fā)現(xiàn)DispatcherServletFrameworkServletContextLoaderListenerDelegatingFilterProxy現(xiàn)在都支持構(gòu)造函數(shù)參數(shù)。Servlet 3.0 ServletContext API允許以編程方式設(shè)置init-paramscontext-params等。

完全基于代碼的配置方法

在上面的例子中,WEB-INF/web.xmlWebApplicationInitializer形式的代碼替換,但dispatcher-config.xml配置仍然是基于XML的。WebApplicationInitializer非常適合與Spring的基于代碼的@Configuration類一起使用。以下示例演示了使用Spring的AnnotationConfigWebApplicationContext代替XmlWebApplicationContext進(jìn)行重構(gòu),以及使用用戶定義的@ConfigurationAppConfigDispatcherConfig,而不是Spring XML文件。這個(gè)例子也超出了上面的例子來(lái)演示根應(yīng)用上下文的典型配置和ContextLoaderListener的注冊(cè):

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

作為上述的替代方法,您還可以繼承自org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer。注意,WebApplicationInitializer的實(shí)現(xiàn)類會(huì)被自動(dòng)檢測(cè)到。

Ordering WebApplicationInitializer execution

WebApplicationInitializer實(shí)現(xiàn)類可以有選擇地在類上使用Spring的@Order注解,也可以實(shí)現(xiàn)Spring的Ordered接口。如果是這樣,初始化程序?qū)⒃谡{(diào)用之前排序。這為用戶提供了確保Servlet容器初始化順序的機(jī)制。使用此功能的情況很少,因?yàn)榈湫偷膽?yīng)用程序可能會(huì)將所有容器初始化集中在一個(gè)WebApplicationInitializer中。

注意事項(xiàng)

web.xml版本

WEB-INF/web.xmlWebApplicationInitializer的使用不是互斥的; 例如,web.xml可以注冊(cè)一個(gè)servlet,而WebApplicationInitializer可以注冊(cè)另一個(gè)。 Initializer甚至可以通過(guò)諸如ServletContext.getServletRegistration(String)之類的方法來(lái)修改在web.xml中執(zhí)行的注冊(cè)。但是,如果應(yīng)用程序中存在WEB-INF/web.xml,則其版本屬性必須設(shè)置為"3.0"或更高,否則Servlet容器將忽略ServletContainerInitializer的引導(dǎo)。


下面我們來(lái)看一組WebApplicationInitializer的實(shí)現(xiàn)類:

繼承關(guān)系如下:

AbstractAnnotationConfigDispatcherServletInitializer
        |
        | —— AbstractDispatcherServletInitializer
                   |
                   | —— AbstractContextLoaderInitializer
                             |
                             | ——  WebApplicationInitializer

AbstractAnnotationConfigDispatcherServletInitializer

org.springframework.web.WebApplicationInitializer實(shí)現(xiàn)類的基類,用于注冊(cè)配置了@Configuration/@Component注解標(biāo)記的配置類DispatcherServlet

具體的實(shí)現(xiàn)類需要實(shí)現(xiàn)getRootConfigClasses()getServletConfigClasses()以及getServletMappings()方法。更多的方法由AbstractDispatcherServletInitializer提供。這是使用基于Java配置應(yīng)用程序的首選方法。

下面是AbstractAnnotationConfigDispatcherServletInitializer的源碼:

public abstract class AbstractAnnotationConfigDispatcherServletInitializer
        extends AbstractDispatcherServletInitializer {

    /**
     * 創(chuàng)建要提供給`ContextLoaderListener`的**根應(yīng)用程序上下文**。
     * <p>
     * 返回的上下文委托給`ContextLoaderListener.ContextLoaderListener(WebApplicationContext)`,
     * 并將作為`DispatcherServlet`應(yīng)用程序上下文的父上下文來(lái)建立。
     * <p>
     * 因此,它通常包含中間層服務(wù),數(shù)據(jù)源等。
     * <p>
     * 該方法創(chuàng)建一個(gè)`AnnotationConfigWebApplicationContext`,并為其提供由`getRootConfigClasses()`返回的配置類。如果`getRootConfigClasses()`返回Null,則不會(huì)創(chuàng)建根應(yīng)用上下文。
     */
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        Class<?>[] configClasses = getRootConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
            rootAppContext.register(configClasses);
            return rootAppContext;
        }
        else {
            return null;
        }
    }

    /**
     * 創(chuàng)建一個(gè)**Servlet應(yīng)用上下文**以提供給`DispatcherServlet`。
     * <p>
     * 返回的上下文被委托給Spring的`DispatcherServlet.DispatcherServlet(WebApplicationContext)`方法。
     * <p>
     * 因此,它通常包含控制器,視圖解析器,locale解析器和其他Web相關(guān)的bean。
     * <p>
     * 該實(shí)現(xiàn)創(chuàng)建一個(gè)`AnnotationConfigWebApplicationContext`,為其提供由`getServletConfigClasses()`返回的配置類。
     */
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
        Class<?>[] configClasses = getServletConfigClasses();
        if (!ObjectUtils.isEmpty(configClasses)) {
            servletAppContext.register(configClasses);
        }
        return servletAppContext;
    }

    /**
     * 指定要提供給根應(yīng)用上下文的`@Configuration`或`@Component`注解標(biāo)記的配置類。
     */
    protected abstract Class<?>[] getRootConfigClasses();

    /**
     * 指定要提供給Dispatcher Servlet應(yīng)用上下文的`@Configuration`或`@Component`注解標(biāo)記的配置類。
     */
    protected abstract Class<?>[] getServletConfigClasses();

}

AbstractDispatcherServletInitializer

org.springframework.web.WebApplicationInitializer實(shí)現(xiàn)的基類,在ServletContext中注冊(cè)DispatcherServlet

具體的實(shí)現(xiàn)類需要實(shí)現(xiàn)createServletApplicationContext()getServletMappings()方法,兩者都由registerDispatcherServlet(ServletContext)調(diào)用。

進(jìn)一步的自定義可以通過(guò)重寫customizeRegistration(ServletRegistration.Dynamic)方法來(lái)實(shí)現(xiàn)。

由于此類繼承了AbstractContextLoaderInitializer抽象類,具體實(shí)現(xiàn)類也需要實(shí)現(xiàn)createRootApplicationContext()來(lái)設(shè)置父級(jí)根應(yīng)用上下文。如果不需要根應(yīng)用上下文,createRootApplicationContext()返回null即可。

下面看下源碼:


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";


    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        super.onStartup(servletContext);
        registerDispatcherServlet(servletContext);
    }

    /**
     * 針對(duì)給定的Servlet上下文注冊(cè)一個(gè)`DispatcherServlet`。
     * <p>
     * 該方法將創(chuàng)建一個(gè)名稱為由`getServletName()`指定的`DispatcherServlet`,
     * 并使用從`createServletApplicationContext()`返回的Servlet應(yīng)用上下文對(duì)其進(jìn)行初始化,
     * 并將其映射到從`getServletMappings()`返回的`pattern`。
     * <p>
     * 進(jìn)一步的自定義可以通過(guò)重寫`customizeRegistration(ServletRegistration.Dynamic)`或`createDispatcherServlet(WebApplicationContext)`來(lái)實(shí)現(xiàn)。
     *
     * @param servletContext 注冊(cè)servlet的上下文
     */
    protected void registerDispatcherServlet(ServletContext servletContext) {
        // DispatcherServlet被注冊(cè)的名稱
        String servletName = getServletName();
        Assert.hasLength(servletName, "getServletName() must not return empty or null");

        // 創(chuàng)建Servlet應(yīng)用上下文,參見(jiàn)AbstractAnnotationConfigDispatcherServletInitializer的實(shí)現(xiàn)
        WebApplicationContext servletAppContext = createServletApplicationContext();
        Assert.notNull(servletAppContext,
                "createServletApplicationContext() did not return an application " +
                        "context for servlet [" + servletName + "]");

        // 將Servlet應(yīng)用上下文以委托給DispatcherServlet
        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());

        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
        Assert.notNull(registration,
                "Failed to register servlet with name '" + servletName + "'." +
                        "Check if there is another servlet registered under the same name.");

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

    /**
     * 返回`DispatcherServlet`被注冊(cè)的名稱。 默認(rèn)值為DEFAULT_SERVLET_NAME。
     *
     * @see #registerDispatcherServlet(ServletContext)
     */
    protected String getServletName() {
        return DEFAULT_SERVLET_NAME;
    }

    /**
     * 創(chuàng)建一個(gè)Servlet應(yīng)用上下文以提供給`DispatcherServlet`。
     * 返回的上下文被委托給Spring的`DispatcherServlet.DispatcherServlet(WebApplicationContext)`。
     * <p>
     * 因此,它通常包含控制器,視圖解析器,locale解析器和其他Web相關(guān)的bean。
     *
     * @see #registerDispatcherServlet(ServletContext)
     */
    protected abstract WebApplicationContext createServletApplicationContext();

    /**
     * 使用指定的`WebApplicationContext`Servlet應(yīng)用上下文創(chuàng)建`DispatcherServlet`(或由`FrameworkServlet`派生的其他類型的`dispatcher`)。
     * <p>
     * 注意:從4.2.3開始允許返回任意`FrameworkServlet`的子類。
     * 以前,它必須要返回一個(gè)`DispatcherServlet`或其子類。
     */
    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.
     *
     * @see #createServletApplicationContext()
     * @see DispatcherServlet#setContextInitializers
     * @see #getRootApplicationContextInitializers()
     * @since 4.2
     */
    protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() {
        return null;
    }

    /**
     * 指定`DispatcherServlet`的servlet映射 - 例如"/", "/app"等
     *
     * @see #registerDispatcherServlet(ServletContext)
     */
    protected abstract String[] getServletMappings();

    /**
     * 指定添加到`ServletContext`,并映射到`DispatcherServlet`的過(guò)濾器。
     *
     * @return an array of filters or {@code null}
     * @see #registerServletFilter(ServletContext, Filter)
     */
    protected Filter[] getServletFilters() {
        return null;
    }

    /**
     * 將給定的過(guò)濾器添加到`ServletContext`并將其映射到`DispatcherServlet`,如下所示:
     * <ul>
     * <li>1. 根據(jù)具體的類型選擇默認(rèn)的過(guò)濾器名稱;
     * <li>2. 異步支持是根據(jù)`asyncSupported`的返回值設(shè)置的;
     * <li>3. 使用`dispatcher`類型為REQUEST,F(xiàn)ORWARD,INCLUDE和ASYNC(取決于asyncSupported的返回值)創(chuàng)建過(guò)濾器映射
     * </ul>
     * <p>
     * 如果上面的默認(rèn)值不合適,重寫這個(gè)方法并直接用`ServletContext`注冊(cè)過(guò)濾器。
     *
     * @param servletContext the servlet context to register filters with
     * @param filter         the filter to be registered
     * @return the filter registration
     */
    protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) {
        // 獲取要注冊(cè)的filter的名稱
        String filterName = Conventions.getVariableName(filter);
        Dynamic registration = servletContext.addFilter(filterName, filter);
        if (registration == null) {// 注冊(cè)失敗,名稱追加序號(hào)并重試
            int counter = -1;
            while (counter == -1 || registration == null) {
                counter++;
                registration = servletContext.addFilter(filterName + "#" + counter, filter);
                Assert.isTrue(counter < 100,
                        "Failed to register filter '" + filter + "'." +
                                "Could the same Filter instance have been registered already?");
            }
        }
        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));
    }

    /**
     * `DispatcherServlet`和通過(guò)`getServletFilters()`添加的所有過(guò)濾器的異步支持標(biāo)記位
     */
    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) {
    }

}

?? 補(bǔ)充:registration.addMappingForServletNames
為當(dāng)前FilterRegistration所代表的Filter添加一個(gè)具有指定Servlet名稱和dispatcher類型的過(guò)濾器映射。過(guò)濾器映射按照添加的順序進(jìn)行匹配。根據(jù)isMatchAfter參數(shù)的值,來(lái)確定給定的過(guò)濾器映射在ServletContext(從中獲取當(dāng)前FilterRegistration)的任何聲明的過(guò)濾器映射之前或之后匹配。

如果這個(gè)方法被多次調(diào)用,每個(gè)連續(xù)的調(diào)用都會(huì)作用于前者。

Parameters:

dispatcherTypes - 過(guò)濾器映射的dispatcher類型,如果要使用默認(rèn)的DispatcherType.REQUEST,則為null

isMatchAfter - 如果給定的過(guò)濾器映射在任何聲明的過(guò)濾器映射之后匹配,則為true;如果在從中獲取此FilterRegistrationServletContext的任何聲明的過(guò)濾器映射之前匹配,則為false

servletNames - 過(guò)濾器映射的Servlet名稱

AbstractContextLoaderInitializer

WebApplicationInitializer實(shí)現(xiàn)類的基類,在ServletContext中注冊(cè)ContextLoaderListener。需要由子類實(shí)現(xiàn)的唯一方法是createRootApplicationContext(),它在registerContextLoaderListener(ServletContext)中被調(diào)用。


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

    /**
     * 針對(duì)給定的`ServletContext`注冊(cè)一個(gè)`ContextLoaderListener`。
     * `ContextLoaderListener`使用從`createRootApplicationContext()`方法返回的根應(yīng)用上下文進(jìn)行初始化。
     * 
     * @param servletContext the servlet context to register the listener against
     */
    protected void registerContextLoaderListener(ServletContext servletContext) {
        WebApplicationContext rootAppContext = createRootApplicationContext();
        if (rootAppContext != null) {
            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
     */
    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
     */
    protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() {
        return null;
    }

}

上面說(shuō)了這么多,簡(jiǎn)單總結(jié)下。

如圖所示:


SpringMVC啟動(dòng)原理(API版).jpg

ContextLoaderListener

上面提到了ContextLoaderListener,下面來(lái)說(shuō)下這個(gè)類的作用。

ServletContextListener

首先看下javax.servlet.ServletContextListener

ServletContextListener為一個(gè)接口,聲明如下:

public interface ServletContextListener extends EventListener {

ServletContextListener接口用于接收有關(guān)javax.servlet.ServletContext生命周期改變的通知事件。

為了接收這些通知事件,其實(shí)現(xiàn)類有如下三種方式聲明:

  • Web應(yīng)用程序的部署描述符(deployment descriptor of the web application,web.xml)中聲明<listener>
  • 使用javax.servlet.annotation.WebListener進(jìn)行注釋
  • 通過(guò)javax.servlet.ServletContext上定義的javax.servlet.ServletContext.addListener方法注冊(cè)

這個(gè)接口實(shí)現(xiàn)類的contextInitialized方法按照它們的聲明順序被調(diào)用, 而contextDestroyed方法則以相反的順序被調(diào)用。


import javax.servlet.ServletContextEvent;
import java.util.EventListener;

/**
 * 用于接收有關(guān){@link javax.servlet.ServletContext}生命周期改變的通知事件的接口。
 * <p>
 * 為了接收這些通知事件,實(shí)現(xiàn)類必須
 * 在Web應(yīng)用程序的部署描述符(deployment descriptor of the web application)中聲明、
 * 使用{@link javax.servlet.annotation.WebListener}進(jìn)行注釋,
 * 或者通過(guò){@link javax.servlet.ServletContext}上定義的{@link javax.servlet.ServletContext#addListener}方法注冊(cè)。
 * <p>
 * 這個(gè)接口實(shí)現(xiàn)類的{@link #contextInitialized}方法按照它們的聲明順序被調(diào)用,
 * 而{@link #contextDestroyed}方法則以相反的順序被調(diào)用。
 *
 * @see ServletContextEvent
 * @since Servlet 2.3
 */
public interface ServletContextListener extends EventListener {

    /**
     * 接收Web應(yīng)用程序初始化過(guò)程正在啟動(dòng)的通知。
     * <p>
     * 在Web應(yīng)用程序中的任何一個(gè)filter或servlet被初始化之前,所有ServletContextListeners都會(huì)收到上下文初始化的通知。
     *
     * @param sce 包含正在初始化的ServletContext的ServletContextEvent
     */
    public void contextInitialized(ServletContextEvent sce);

    /**
     * 接收ServletContext即將關(guān)閉的通知。
     * <p>
     * 在任何ServletContextListeners收到上下文銷毀通知之前,所有servlet和filter都將被銷毀。
     *
     * @param sce 包含正在被銷毀的ServletContext的ServletContextEvent
     */
    public void contextDestroyed(ServletContextEvent sce);
}

ContextLoaderListener

然后再看下ContextLoaderListener

聲明如下:

public class ContextLoaderListener 
        extends ContextLoader implements ServletContextListener {

引導(dǎo)listener啟動(dòng)和關(guān)閉Spring的根WebApplicationContext。它只是
簡(jiǎn)單地委托給ContextLoader(繼承)以及ContextCleanupListener(在contextDestroyed方法中調(diào)用)。

該listener應(yīng)該在web.xml中的org.springframework.web.util.Log4jConfigListener(該類已標(biāo)記過(guò)時(shí))之后進(jìn)行注冊(cè)。

從Spring 3.1開始,ContextLoaderListener支持通過(guò)ContextLoaderListener(WebApplicationContext)構(gòu)造函數(shù)注入根Web應(yīng)用程序上下文, 從而允許在Servlet 3.0+環(huán)境中進(jìn)行編程式配置。

ContextLoaderListener()

創(chuàng)建一個(gè)新的ContextLoaderListener, 它將基于"contextClass"和"contextConfigLocation" Servlet <context-params>參數(shù)創(chuàng)建一個(gè)Web應(yīng)用程序上下文。
當(dāng)在web.xml中聲明ContextLoaderListener<listener>時(shí),通常使用這個(gè)構(gòu)造函數(shù)。
創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext屬性WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE下, 并且當(dāng)在此listener上調(diào)用contextDestroyed方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。

ContextLoaderListener(WebApplicationContext context)

給定的應(yīng)用程序上下文創(chuàng)建一個(gè)新的ContextLoaderListener。 這個(gè)構(gòu)造函數(shù)用于Servlet 3.0+,通過(guò)javax.servlet.ServletContext.addListener API可以實(shí)現(xiàn)基于實(shí)例的listeners注冊(cè)。
上下文可能或尚未刷新。 如果

  • (a)是ConfigurableWebApplicationContext的實(shí)現(xiàn).
  • (b)尚未刷新(推薦的方法)

滿足上面兩個(gè)條件,則會(huì)發(fā)生以下情況:

  • 如果給定的上下文還沒(méi)有被分配一個(gè)id,則將被分配一個(gè)
  • ServletContextServletConfig對(duì)象將被委托給應(yīng)用程序上下文
  • customizeContext將被調(diào)用
  • 任何通過(guò)"contextInitializerClasses" init-param指定的ApplicationContextInitializers將被應(yīng)用。
  • refresh()將被調(diào)用

如果上下文已經(jīng)被刷新或者沒(méi)有實(shí)現(xiàn)ConfigurableWebApplicationContext,上述任何一種情況都不會(huì)發(fā)生。
創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext屬性WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE下, 并且當(dāng)在此listener上調(diào)用contextDestroyed方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。

import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.context.ContextCleanupListener;
import org.springframework.web.context.ContextLoader;
import org.springframework.web.context.WebApplicationContext;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * 引導(dǎo)listener啟動(dòng)和關(guān)閉Spring的根{@link WebApplicationContext}。
 * 簡(jiǎn)單地委托給{@link ContextLoader}以及{@link ContextCleanupListener}。
 * <p>
 * 該listener應(yīng)該在{@code web.xml}中的{@link org.springframework.web.util.Log4jConfigListener}之后進(jìn)行注冊(cè),如果使用它的話。
 * <p>
 * 從Spring 3.1開始,{@code ContextLoaderListener}支持通過(guò){@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)}構(gòu)造函數(shù)注入根Web應(yīng)用程序上下文,
 * 從而允許在Servlet 3.0+環(huán)境中進(jìn)行編程式配置。
 *
 * @author Juergen Hoeller
 * @author Chris Beams
 * @see #setContextInitializers
 * @see org.springframework.web.WebApplicationInitializer
 * @see org.springframework.web.util.Log4jConfigListener
 * @since 17.02.2003
 */
public class ContextLoaderListener extends ContextLoader implements ServletContextListener {

    /**
     * 創(chuàng)建一個(gè)新的{@code ContextLoaderListener},
     * 它將基于"contextClass"和"contextConfigLocation" Servlet {@code <context-params>}參數(shù)創(chuàng)建一個(gè)Web應(yīng)用程序上下文。
     * 請(qǐng)參閱{@link ContextLoader}父類文檔以獲取詳細(xì)信息。
     * <p>
     * 當(dāng)在{@code web.xml}中聲明{@code ContextLoaderListener}為{@code <listener>}時(shí),通常使用這個(gè)構(gòu)造函數(shù)。
     * <p>
     * 創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext屬性{@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}下,
     * 并且當(dāng)在此listener上調(diào)用{@link #contextDestroyed}方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。
     *
     * @see ContextLoader
     * @see #ContextLoaderListener(WebApplicationContext)
     * @see #contextInitialized(ServletContextEvent)
     * @see #contextDestroyed(ServletContextEvent)
     */
    public ContextLoaderListener() {
    }

    /**
     * 用<strong>給定的應(yīng)用程序上下文</strong>創(chuàng)建一個(gè)新的{@code ContextLoaderListener}。
     * 這個(gè)構(gòu)造函數(shù)用于Servlet 3.0+,通過(guò){@link javax.servlet.ServletContext#addListener} API可以實(shí)現(xiàn)基于實(shí)例的listeners注冊(cè)。
     * <p>
     * 上下文可能或尚未{@linkplain org.springframework.context.ConfigurableApplicationContext#refresh() 刷新}。
     * 如果(a)是{@link ConfigurableWebApplicationContext}的實(shí)現(xiàn),并且(b)<strong>尚未刷新</strong>(推薦的方法),則會(huì)發(fā)生以下情況:
     * <ul>
     * <li>如果給定的上下文還沒(méi)有被分配一個(gè){@linkplain org.springframework.context.ConfigurableApplicationContext#setId id},則將被分配一個(gè)
     * <li>{@code ServletContext}和{@code ServletConfig}對(duì)象將被委托給應(yīng)用程序上下文
     * <li>{@link #customizeContext}將被調(diào)用
     * <li>任何通過(guò)"contextInitializerClasses" init-param指定的{@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s將被應(yīng)用。
     * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()}將被調(diào)用
     * </ul>
     * <p>
     * 如果上下文已經(jīng)被刷新或者沒(méi)有實(shí)現(xiàn){@code ConfigurableWebApplicationContext},上述任何一種情況都不會(huì)發(fā)生。
     * <p>
     * 創(chuàng)建的應(yīng)用程序上下文將被注冊(cè)到ServletContext屬性{@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}下,
     * 并且當(dāng)在此listener上調(diào)用{@link #contextDestroyed}方法時(shí),Spring應(yīng)用程序上下文將被關(guān)閉。
     *
     * @param context the application context to manage
     * @see #contextInitialized(ServletContextEvent)
     * @see #contextDestroyed(ServletContextEvent)
     */
    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }


    /**
     * Initialize the root web application context.
     */
    @Override
    public void contextInitialized(ServletContextEvent event) {
        initWebApplicationContext(event.getServletContext());
    }


    /**
     * Close the root web application context.
     */
    @Override
    public void contextDestroyed(ServletContextEvent event) {
        closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }

}

ContextLoader

聲明如下:

public class ContextLoader {

為給定的servlet上下文初始化Spring的Web應(yīng)用程序上下文,
使用構(gòu)造時(shí)提供的應(yīng)用程序上下文, 或者根據(jù)"contextClass" 和"contextConfigLocation" <context-param>創(chuàng)建一個(gè)新的。

方式1 通過(guò)web.xml配置

web.xml <context-param> 中查找"contextClass"參數(shù)以指定上下文類型, 如果未找到則使用org.springframework.web.context.support.XmlWebApplicationContext

默認(rèn),指定的任何上下文類都需要實(shí)現(xiàn)ConfigurableWebApplicationContext接口。

處理web.xml <context-param> 中的"contextConfigLocation"參數(shù), 并將其值傳遞給上下文實(shí)例,其可能為多個(gè)配置文件路徑(用逗號(hào)和空格分隔)。例如,"WEB-INF/applicationContext1.xml, WEB-INF/applicationContext2.xml"。 還支持Ant樣式的路徑模式,例如,"WEB-INF/Context.xml,WEB-INF/spring.xml" 或者 "WEB-INF/*/Context.xml"。 如果沒(méi)有明確指定,上下文實(shí)現(xiàn)應(yīng)該使用默認(rèn)位置(對(duì)于XmlWebApplicationContext,"/WEB-INF/applicationContext.xml")。

注意:在多個(gè)配置文件的情況下,后面的bean定義將覆蓋之前加載的文件中的定義,至少在使用Spring的默認(rèn)ApplicationContext實(shí)現(xiàn)時(shí)是這樣。 這可以用來(lái)通過(guò)一個(gè)額外的XML文件重寫某些bean定義。

方式2 通過(guò)Java Config配置

從Spring 3.1開始,ContextLoader支持通過(guò)ContextLoader(WebApplicationContext)構(gòu)造函數(shù)注入根Web應(yīng)用程序上下文, 從而允許在Servlet 3.0+環(huán)境中進(jìn)行編程式配置。 有關(guān)使用示例,請(qǐng)參閱org.springframework.web.WebApplicationInitializer



總結(jié)

介紹了這么多類,這里做一個(gè)簡(jiǎn)短的總結(jié)。

Servlet3.0+規(guī)范后,允許Servlet,F(xiàn)ilter,Listener不必聲明在web.xml中,而是以Java Config的方式編碼存在,實(shí)現(xiàn)容器的零配置。

ServletContainerInitializer啟動(dòng)容器時(shí)負(fù)責(zé)加載相關(guān)配置。

package javax.servlet;

import java.util.Set;

public interface ServletContainerInitializer {
    void onStartup(Set<Class<?>> var1, ServletContext var2) throws ServletException;
}

Servlet容器啟動(dòng)時(shí)會(huì)自動(dòng)掃描當(dāng)前服務(wù)中ServletContainerInitializer的實(shí)現(xiàn)類。并調(diào)用其onStartup方法,其參數(shù)Set<Class<?>> c可通過(guò)在實(shí)現(xiàn)類上聲明注解javax.servlet.annotation.HandlesTypes(xxx.class)注解自動(dòng)注入。
@HandlesTypes會(huì)自動(dòng)掃描項(xiàng)目中所有的xxx.class的實(shí)現(xiàn)類,并將其全部注入Set。

Spring為其提供了一個(gè)實(shí)現(xiàn)類:SpringServletContainerInitializer類。通過(guò)查看源碼可以看出,WebApplicationInitializer才是我們需要關(guān)心的接口。

我們只需要將相應(yīng)的ServletFilterListener等硬編碼到該接口的實(shí)現(xiàn)類中即可。

Spring為我們提供了一些WebApplicationInitializer的抽象類,我們只需要繼承并按需修改即可。常見(jiàn)的實(shí)現(xiàn)類有:

AbstractAnnotationConfigDispatcherServletInitializer
AbstractDispatcherServletInitializer
AbstractContextLoaderInitializer
AbstractHttpSessionApplicationInitializer

對(duì)于一個(gè)web應(yīng)用,其部署在web容器中,web容器提供其一個(gè)全局的上下文環(huán)境,這個(gè)上下文就是ServletContext,其為后面的Spring IoC容器提供宿主環(huán)境;

其次,在web.xml中會(huì)提供有ContextLoaderListener

在web容器啟動(dòng)時(shí),會(huì)觸發(fā)容器初始化事件,此時(shí)ContextLoaderListener會(huì)監(jiān)聽到這個(gè)事件,其contextInitialized方法會(huì)被調(diào)用。


package org.springframework.web.context;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
    public ContextLoaderListener() {
    }

    public ContextLoaderListener(WebApplicationContext context) {
        super(context);
    }

    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

    public void contextDestroyed(ServletContextEvent event) {
        this.closeWebApplicationContext(event.getServletContext());
        ContextCleanupListener.cleanupAttributes(event.getServletContext());
    }
}

contextInitialized這個(gè)方法中,spring會(huì)初始化一個(gè)啟動(dòng)上下文,這個(gè)上下文被稱為根上下文,即WebApplicationContext,這是一個(gè)接口類。

確切的說(shuō),其實(shí)現(xiàn)類是XmlWebApplicationContext(基于web.xml配置)或者上面提及的AnnotationConfigWebApplicationContext(基于JavaConfig配置)。

這個(gè)就是Spring IoC容器,其對(duì)應(yīng)的自定義的配置由web.xml中的<context-param>標(biāo)簽指定。

在這個(gè)IoC容器初始化完畢后,TODO Spring以WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE為屬性Key,將其存儲(chǔ)到ServletContext中,便于獲取。

public interface WebApplicationContext extends ApplicationContext {
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
}
public class ContextLoader {
    ...
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        ...
        // 以`WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE`為屬性Key,將根上下文存儲(chǔ)到ServletContext中
        servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
        ...
    }
    ...
}

再次,ContextLoaderListener監(jiān)聽器初始化完畢后,開始初始化web.xml中配置的Servlet,這個(gè)servlet可以配置多個(gè),以最常見(jiàn)的DispatcherServlet為例,

這個(gè)servlet實(shí)際上是一個(gè)標(biāo)準(zhǔn)的前端控制器,用以轉(zhuǎn)發(fā)、匹配、處理每個(gè)servlet請(qǐng)求。

DispatcherServlet上下文在初始化的時(shí)候會(huì)建立自己的IoC上下文,用以持有spring mvc相關(guān)的bean。

在建立DispatcherServlet自己的IoC上下文時(shí),TODO 會(huì)利用WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE先從ServletContext中獲取之前的根上下文(即WebApplicationContext)作為自己上下文的parent上下文。
有了這個(gè)parent上下文之后,再初始化自己持有的上下文。

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

    protected WebApplicationContext initWebApplicationContext() {
        ...
        if (cwac.getParent() == null) {
            // The context instance was injected without an explicit parent -> set
            // the root application context (if any; may be null) as the parent
            cwac.setParent(rootContext);
        }
        ...
    }
}

這個(gè)DispatcherServlet初始化自己上下文的工作在其initStrategies方法中可以看到,大概的工作就是初始化處理器映射、視圖解析等。

public class DispatcherServlet extends FrameworkServlet {
    
    protected void initStrategies(ApplicationContext context) {
            this.initMultipartResolver(context);
            this.initLocaleResolver(context);
            this.initThemeResolver(context);
            this.initHandlerMappings(context);
            this.initHandlerAdapters(context);
            this.initHandlerExceptionResolvers(context);
            this.initRequestToViewNameTranslator(context);
            this.initViewResolvers(context);
            this.initFlashMapManager(context);
        }
    
}

這個(gè)servlet自己持有的上下文默認(rèn)實(shí)現(xiàn)類也是XmlWebApplicationContext,當(dāng)然也可以基于JavaConfig方式配置AnnotationConfigWebApplicationContext

初始化完畢后,TODO spring以與servlet的名字相關(guān)(此處不是簡(jiǎn)單的以servlet名為Key,而是通過(guò)一些轉(zhuǎn)換)的屬性為屬性Key,也將其存到ServletContext中,以便后續(xù)使用。

public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {

    public static final String SERVLET_CONTEXT_PREFIX = FrameworkServlet.class.getName() + ".CONTEXT.";

    protected WebApplicationContext initWebApplicationContext() {
        ...
        if (this.publishContext) {
            // Publish the context as a servlet context attribute.
            String attrName = getServletContextAttributeName();
            getServletContext().setAttribute(attrName, wac);
        }
        ...
    }

    /**
     * Return the ServletContext attribute name for this servlet's WebApplicationContext.
     * <p>The default implementation returns
     * {@code SERVLET_CONTEXT_PREFIX + servlet name}.
     * @see #SERVLET_CONTEXT_PREFIX
     * @see #getServletName
     */
    public String getServletContextAttributeName() {
        return SERVLET_CONTEXT_PREFIX + getServletName();
    }
}

這樣每個(gè)servlet就持有自己的上下文,即擁有自己獨(dú)立的bean空間,同時(shí)各個(gè)servlet共享相同的bean,即根上下文定義的那些bean。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,443評(píng)論 6 532
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,530評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,407評(píng)論 0 375
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,981評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,759評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,204評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,263評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,415評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,955評(píng)論 1 336
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,782評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,983評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,528評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,222評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,650評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,892評(píng)論 1 286
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,675評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,967評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容

  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,898評(píng)論 6 342
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,783評(píng)論 18 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,320評(píng)論 11 349
  • 本章聊一聊ServletContext 3.0規(guī)范中定義的注解以及在web應(yīng)用中使用的框架和庫(kù)的可插拔性的提升。 ...
    Lucky_Micky閱讀 6,053評(píng)論 0 3
  • 今天的我,很傻。中午買了一份盒飯,在酒店看著肥皂劇吃完,倒頭就睡在了床上,就是一頭豬的生活節(jié)奏。
    一個(gè)世紀(jì)閱讀 137評(píng)論 0 0