SpringMVC是怎么添加DispatcherServlet到Servlet容器中

1. Servlet與SpringMVC之間的關(guān)係

Spring的MVC是基於Servlet功能實(shí)現(xiàn)的,經(jīng)過實(shí)現(xiàn)Servlet接口的DispatcherServlet來封裝其核心功能實(shí)現(xiàn)。

2. ServletContainerInitializer接口

在web容器啓動(dòng)時(shí)會(huì)作一些初始化的工做,例如註冊(cè)servlet或者filtes等,servlet規(guī)範(fàn)中經(jīng)過ServletContainerInitializer實(shí)現(xiàn)此功能。

每一個(gè)框架, 比如Spring,要使用ServletContainerInitializer就必須在對(duì)應(yīng)的jar包的META-INF/services 目錄建立一個(gè)名爲(wèi)javax.servlet.ServletContainerInitializer的文件,文件內(nèi)容指定具體的ServletContainerInitializer實(shí)現(xiàn)類。(JAVA的SPI特性

案例演示:服務(wù)器

@HandlesTypes(value = MyHandlesType.class)//該註解聲明的類,會(huì)被注入到set中,若是沒有合適的類,set爲(wèi)null,這個(gè)動(dòng)作也是Servlet 容器做的(如tomcat)
public class MyServletContainerInitializer implements ServletContainerInitializer {

    /**
     * @param set 感興趣類型 也就是MyHandlesType 全部子類型
     * @param servletContext
     * @throws ServletException
     */
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        // 1.打印全部感興趣的類型
        for (Class<?> c : set) {
            System.out.println(c);
        }
       // 2.servletContext 手動(dòng)註冊(cè)過濾器、servlet、監(jiān)聽器
        ServletRegistration.Dynamic payServlet = servletContext.addServlet("payServlet", new PayServlet());
        payServlet.addMapping("/pay");
    }
}

以前傳統(tǒng)的Servlet開發(fā),我也需要提供web.xml到Tomcat中,在web.xml中指定Servlet,以及servlet mapping,如:

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- spring mvc配置開始  -->
    <servlet>
        <servlet-name>let'sGo</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>let'sGo</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- spring mvc配置結(jié)束  -->

    <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>
</web-app>

springmvc是如何實(shí)現(xiàn)不需要web.xml配置,靠的就是ServletContainerInitialize。

3. SpringServletContainerInitializer的作用

看下SpringServletContainerInitializer的源碼:

@HandlesTypes(WebApplicationInitializer.class)  // Servlet容器會(huì)根據(jù)@HandlesTypes找到所有注解中的類的實(shí)現(xiàn),也就是所有WebApplicationInitializer.class的實(shí)現(xiàn)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    
    //所有WebApplicationInitializer.class的實(shí)現(xiàn)都會(huì)被放在這個(gè)Set中
   @Override
   public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
         throws ServletException {

      List<WebApplicationInitializer> initializers = Collections.emptyList();

      if (webAppInitializerClasses != null) {
         initializers = new ArrayList<>(webAppInitializerClasses.size());
         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 {
                  initializers.add((WebApplicationInitializer)
                        ReflectionUtils.accessibleConstructor(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);
      // 調(diào)用所有WebApplicationInitializer實(shí)現(xiàn)類的onStartUp()函數(shù)
      for (WebApplicationInitializer initializer : initializers) {
         initializer.onStartup(servletContext);
      }
   }
}
  • @HandlesTypes(WebApplicationInitializer.class) // Servlet容器會(huì)根據(jù)@HandlesTypes找到所有注解中的類的實(shí)現(xiàn),也就是所有WebApplicationInitializer.class的實(shí)現(xiàn)
  • 所有WebApplicationInitializer.class的實(shí)現(xiàn)都會(huì)被放在這個(gè)Set中
  • 調(diào)用所有WebApplicationInitializer實(shí)現(xiàn)類的onStartUp()函數(shù),這樣Spring框架就可以在WebApplicationInitializer實(shí)現(xiàn)類里做一些初始化等操作(如創(chuàng)建DispatcherServlet,添加Filter)

SpringServletContainerInitializer通過實(shí)現(xiàn)ServletContainerInitializer將自身并入到Servlet容器的生命周期中, 并通過自身定義的WebApplicationInitializer將依賴于Spring框架的系統(tǒng)初始化需求與Servlet容器解耦. 即依賴于spring的系統(tǒng)可以通過實(shí)現(xiàn)WebApplicationInitializer來實(shí)現(xiàn)自定義的初始化邏輯. 而不需要去實(shí)現(xiàn)ServletContainerInitializer

4. WebApplicationInitializer

SpringServletContainerInitializer會(huì)調(diào)用所有實(shí)現(xiàn)了WebApplicationInitializer接口的實(shí)現(xiàn)類。Spring提供了很多實(shí)現(xiàn)類用來做初始化操作。

5. AbstractAnnotationConfigDispatcherServletInitializer

SpringMVC提供的AbstractAnnotationConfigDispatcherServletInitializer這個(gè)抽象類間接實(shí)現(xiàn)了WebApplicationInitializer接口,如下圖所示。所以我們只需要自己寫一個(gè)類,繼承AbstractAnnotationConfigDispatcherServletInitializer這個(gè)抽象類,servlet容器啟動(dòng)的時(shí)候就會(huì)調(diào)用我們寫的實(shí)現(xiàn)類的父類里的onStartUp()函數(shù)。

image.png

具體其實(shí)就是AbstractDispatcherServletInitializer.class的onStartUp()函數(shù):

//org.springframework.web.servlet.support.AbstractDispatcherServletInitializer
public void onStartup(ServletContext servletContext) throws ServletException {
    super.onStartup(servletContext);    
    registerDispatcherServlet(servletContext);
}

protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = getServletName();    
    Assert.hasLength(servletName, "getServletName() must not return null or empty");   
    // 創(chuàng)建Spring容器,ServletApplicationContext 
    WebApplicationContext servletAppContext = createServletApplicationContext();    
    Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");  
    // 創(chuàng)建DispatcherServlet,并把Spring容器當(dāng)做參數(shù)穿進(jìn)去  
    FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);    
    Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");    
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());  
    // 將創(chuàng)建好的DispatcherServlet傳進(jìn)Servlet容器里  
    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.");    }

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

這樣的話我們就通過代碼的方式做了如下操作:

  1. 創(chuàng)建了Spring容器---ServletApplicationContext
  2. 創(chuàng)建了Servlet---DispatcherServlet,并把Spring容器放進(jìn)了Servlet中
  3. 把Servlet添加到Servlet 容器中。

6. 上述操作,通過web.xml也能做到,不過Servlet規(guī)范3.0以后,支持了代碼的方式, 我們看一下通過web.xml執(zhí)行上述操作的例子:

<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- spring mvc配置開始  -->
    <servlet>
        <servlet-name>let'sGo</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>let'sGo</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!-- spring mvc配置結(jié)束  -->

    <welcome-file-list>
        <welcome-file>index</welcome-file>
    </welcome-file-list>
</web-app>
  1. Servlet容器會(huì)根據(jù)Servlet規(guī)范讀取web.xml文件
  2. 讀到<servlet>標(biāo)簽后就創(chuàng)建<servlet>里標(biāo)記的Servlet——這里是DispatcherServlet,這里創(chuàng)建調(diào)用的是無參構(gòu)造函數(shù),跟前面通過代碼實(shí)現(xiàn)的時(shí)候創(chuàng)建DispatcherServlet用的是帶參數(shù)的構(gòu)造函數(shù)不一樣,因?yàn)檫@里沒有事先創(chuàng)建好的Spring容器(Servlet WebApplicationContext)。
  3. 所以在new DispatcherServlet的過程中,判斷當(dāng)前沒有現(xiàn)成的Spring容器(Servlet WebApplicationContext),就會(huì)自己創(chuàng)建一個(gè)Spring容器,代碼如下
//org.springframework.web.servlet.FrameworkServlet,它是DispatcherServlet的父類
protected WebApplicationContext initWebApplicationContext() {
    WebApplicationContext rootContext =
          WebApplicationContextUtils.getWebApplicationContext(getServletContext());    
          WebApplicationContext wac = null;    
          
          if (this.webApplicationContext != null) {
       // A context instance was injected at construction time -> use it
       // 看下構(gòu)造函數(shù)中是否傳進(jìn)來了  webApplicationContext,用web.xml的情況下,webApplicationContext是null     
       wac = this.webApplicationContext;       
       if (wac instanceof ConfigurableWebApplicationContext) {
          ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;          
          if (!cwac.isActive()) {
             // The context has not yet been refreshed -> provide services such as             
             // setting the parent context, setting the application context id, etc             
             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);             
                }
             configureAndRefreshWebApplicationContext(cwac);          
             }
       }
    }
    if (wac == null) {
       // No context instance was injected at construction time -> see if one      
        // has been registered in the servlet context. If one exists, it is assumed       
        // that the parent context (if any) has already been set and that the       
        // user has performed any initialization such as setting the context id  
        // 看下Servlet容器里有沒有現(xiàn)成的     webApplicationContext
        wac = findWebApplicationContext();    
        }
    // 前面幾步都沒拿到,自己創(chuàng)建一個(gè)webApplicationContext,使用web.xml的情況走的就是這里
    if (wac == null) {
       // No context instance is defined for this servlet -> create a local one      
        wac = createWebApplicationContext(rootContext);    
        }
    ......
}
  • 看下構(gòu)造函數(shù)中是否傳進(jìn)來了 webApplicationContext,用web.xml的情況下,webApplicationContext是null
  • 看下Servlet容器里有沒有現(xiàn)成的 webApplicationContext
  • 前面幾步都沒拿到,自己創(chuàng)建一個(gè)webApplicationContext,使用web.xml的情況走的就是這里
  1. 然后還是DispatcherServlet,它會(huì)把自己添加到Servlet容器中。
// org.springframework.web.servlet.FrameworkServlet
if (this.publishContext) {
    // Publish the context as a servlet context attribute.    
    String attrName = getServletContextAttributeName();    
    getServletContext().setAttribute(attrName, wac);
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容