java SPI 機制 在Tomcat,spring-mvc啓動及servlet3.0中的應用

 

 

寫本篇博客原由:

   最近本人在學習Tomcat源碼,在研讀StandardContext的時候,其中有一個關鍵類ContextConfig,也就是下面代碼:

processServletContainerInitializers(); 方法的調用地方,發現其內部在加載處理 javax.servlet.ServletContainerInitializer,把對象實例保存到 ContextConfig 的 Map 中,待 Wrapper 子容器添加到 StandardContext 子容器中之後,再把 ServletContainerInitializer 加入 ServletContext 中。ServletContainerInitializer 是 servlet3.0 提供的一個 SPI,可以通過 HandlesTypes 篩選出相關的 servlet 類,並可以對 ServletContext 進行額外處理,實現了 ServletContainerInitializer 接口,和 jdk 提供的其它 SPI 一樣,需要在 META-INF/services/javax.servlet.ServletContainerInitializer 文件中指定該類名。
然後便想研究一下  SPI機制在servlet3.0的應用,之前也有一篇關於SPI機制的文章。

https://blog.csdn.net/gaohaicheng123/article/details/105824988

 

 

/**
     * Scan the web.xml files that apply to the web application and merge them
     * using the rules defined in the spec. For the global web.xml files,
     * where there is duplicate configuration, the most specific level wins. ie
     * an application's web.xml takes precedence over the host level or global
     * web.xml file.
     */
    protected void webConfig() {
        /*
         * Anything and everything can override the global and host defaults.
         * This is implemented in two parts
         * - Handle as a web fragment that gets added after everything else so
         *   everything else takes priority
         * - Mark Servlets as overridable so SCI configuration can replace
         *   configuration from the defaults
         */

        /*
         * The rules for annotation scanning are not as clear-cut as one might
         * think. Tomcat implements the following process:
         * - As per SRV.1.6.2, Tomcat will scan for annotations regardless of
         *   which Servlet spec version is declared in web.xml. The EG has
         *   confirmed this is the expected behaviour.
         * - As per http://java.net/jira/browse/SERVLET_SPEC-36, if the main
         *   web.xml is marked as metadata-complete, JARs are still processed
         *   for SCIs.
         * - If metadata-complete=true and an absolute ordering is specified,
         *   JARs excluded from the ordering are also excluded from the SCI
         *   processing.
         * - If an SCI has a @HandlesType annotation then all classes (except
         *   those in JARs excluded from an absolute ordering) need to be
         *   scanned to check if they match.
         */
        WebXmlParser webXmlParser = new WebXmlParser(context.getXmlNamespaceAware(),
                context.getXmlValidation(), context.getXmlBlockExternal());

        Set<WebXml> defaults = new HashSet<>();
        defaults.add(getDefaultWebXmlFragment(webXmlParser));

        Set<WebXml> tomcatWebXml = new HashSet<>();
        tomcatWebXml.add(getTomcatWebXmlFragment(webXmlParser));

        // 創建 WebXml實例,並解析 web.xml 文件
        WebXml webXml = createWebXml();

        // Parse context level web.xml
        InputSource contextWebXml = getContextWebXmlSource();
        if (!webXmlParser.parseWebXml(contextWebXml, webXml, false)) {
            ok = false;
        }

        ServletContext sContext = context.getServletContext();

        // Ordering is important here

        // Step 1. Identify all the JARs packaged with the application and those
        // provided by the container. If any of the application JARs have a
        // web-fragment.xml it will be parsed at this point. web-fragment.xml
        // files are ignored for container provided JARs.
        Map<String,WebXml> fragments = processJarsForWebFragments(webXml, webXmlParser);

        // Step 2. Order the fragments.
        Set<WebXml> orderedFragments = null;
        orderedFragments =
                WebXml.orderWebFragments(webXml, fragments, sContext);

        // Step 3. Look for ServletContainerInitializer implementations
        if (ok) {
        	
        	// !!! 注意此處。。。
            processServletContainerInitializers();
        }

        if  (!webXml.isMetadataComplete() || typeInitializerMap.size() > 0) {
            // Step 4. Process /WEB-INF/classes for annotations and
            // @HandlesTypes matches
            Map<String,JavaClassCacheEntry> javaClassCache = new HashMap<>();

            if (ok) {
                WebResource[] webResources =
                        context.getResources().listResources("/WEB-INF/classes");

                for (WebResource webResource : webResources) {
                    // Skip the META-INF directory from any JARs that have been
                    // expanded in to WEB-INF/classes (sometimes IDEs do this).
                    if ("META-INF".equals(webResource.getName())) {
                        continue;
                    }
                    processAnnotationsWebResource(webResource, webXml,
                            webXml.isMetadataComplete(), javaClassCache);
                }
            }

            // Step 5. Process JARs for annotations and
            // @HandlesTypes matches - only need to process those fragments we
            // are going to use (remember orderedFragments includes any
            // container fragments)
            if (ok) {
                processAnnotations(
                        orderedFragments, webXml.isMetadataComplete(), javaClassCache);
            }

            // Cache, if used, is no longer required so clear it
            javaClassCache.clear();
        }

        if (!webXml.isMetadataComplete()) {
            // Step 6. Merge web-fragment.xml files into the main web.xml
            // file.
            if (ok) {
                ok = webXml.merge(orderedFragments);
            }

            // Step 7a
            // merge tomcat-web.xml
            webXml.merge(tomcatWebXml);

            // Step 7b. Apply global defaults
            // Have to merge defaults before JSP conversion since defaults
            // provide JSP servlet definition.
            webXml.merge(defaults);

            // Step 8. Convert explicitly mentioned jsps to servlets
            if (ok) {
                convertJsps(webXml);
            }

            // Step 9. Apply merged web.xml to Context
            if (ok) {
                configureContext(webXml);
            }
        } else {
            webXml.merge(tomcatWebXml);
            webXml.merge(defaults);
            convertJsps(webXml);
            configureContext(webXml);
        }

        if (context.getLogEffectiveWebXml()) {
            log.info("web.xml:\n" + webXml.toXml());
        }

        // Always need to look for static resources
        // Step 10. Look for static resources packaged in JARs
        if (ok) {
            // Spec does not define an order.
            // Use ordered JARs followed by remaining JARs
            Set<WebXml> resourceJars = new LinkedHashSet<>();
            for (WebXml fragment : orderedFragments) {
                resourceJars.add(fragment);
            }
            for (WebXml fragment : fragments.values()) {
                if (!resourceJars.contains(fragment)) {
                    resourceJars.add(fragment);
                }
            }
            processResourceJARs(resourceJars);
            // See also StandardContext.resourcesStart() for
            // WEB-INF/classes/META-INF/resources configuration
        }

        // Step 11. Apply the ServletContainerInitializer config to the
        // context
        if (ok) {
            for (Map.Entry<ServletContainerInitializer,
                    Set<Class<?>>> entry :
                        initializerClassMap.entrySet()) {
                if (entry.getValue().isEmpty()) {
                    context.addServletContainerInitializer(
                            entry.getKey(), null);
                } else {
                    context.addServletContainerInitializer(
                            entry.getKey(), entry.getValue());
                }
            }
        }
    }

此處是上面和核心部分, 尋找一個 ServletContainerInitializer  容器初始化的類對象:

        // Step 3. Look for ServletContainerInitializer implementations
        if (ok) {
        	
        	// !!! 注意此處。。。
            processServletContainerInitializers();
        }

下面我們點入方法查看一下實現:

/**
     * Scan JARs for ServletContainerInitializer implementations.
        掃描包,尋找實現類
     */
    protected void processServletContainerInitializers() {
        
        List<ServletContainerInitializer> detectedScis;
        try {
            WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
            // 此處是一個關鍵的地方。具體加載如何加載實現類。
            detectedScis = loader.load(ServletContainerInitializer.class);
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.servletContainerInitializerFail",
                    context.getName()),
                e);
            ok = false;
            return;
        }

        for (ServletContainerInitializer sci : detectedScis) {
            initializerClassMap.put(sci, new HashSet<Class<?>>());

            HandlesTypes ht;
            try {
                ht = sci.getClass().getAnnotation(HandlesTypes.class);
            } catch (Exception e) {
                if (log.isDebugEnabled()) {
                    log.info(sm.getString("contextConfig.sci.debug",
                            sci.getClass().getName()),
                            e);
                } else {
                    log.info(sm.getString("contextConfig.sci.info",
                            sci.getClass().getName()));
                }
                continue;
            }
            if (ht == null) {
                continue;
            }
            Class<?>[] types = ht.value();
            if (types == null) {
                continue;
            }

            for (Class<?> type : types) {
                if (type.isAnnotation()) {
                    handlesTypesAnnotations = true;
                } else {
                    handlesTypesNonAnnotations = true;
                }
                Set<ServletContainerInitializer> scis =
                        typeInitializerMap.get(type);
                if (scis == null) {
                    scis = new HashSet<>();
                    typeInitializerMap.put(type, scis);
                }
                scis.add(sci);
            }
        }
    }

關鍵的地方就是這裏

  List<ServletContainerInitializer> detectedScis;
        try {
            WebappServiceLoader<ServletContainerInitializer> loader = new WebappServiceLoader<>(context);
            // 此處是一個關鍵的地方。具體加載如何加載實現類。
            detectedScis = loader.load(ServletContainerInitializer.class);
        } catch (IOException e) {
            log.error(sm.getString(
                    "contextConfig.servletContainerInitializerFail",
                    context.getName()),
                e);
            ok = false;
            return;
        }

下面我們繼續跟入代碼:

public List<T> load(Class<T> serviceType) throws IOException {

        // String SERVICES = "META-INF/services/";  這裏便是祕密。
        String configFile = SERVICES + serviceType.getName();

        LinkedHashSet<String> applicationServicesFound = new LinkedHashSet<>();
        LinkedHashSet<String> containerServicesFound = new LinkedHashSet<>();

        ClassLoader loader = servletContext.getClassLoader();

        // if the ServletContext has ORDERED_LIBS, then use that to specify the
        // set of JARs from WEB-INF/lib that should be used for loading services
        @SuppressWarnings("unchecked")
        List<String> orderedLibs =
                (List<String>) servletContext.getAttribute(ServletContext.ORDERED_LIBS);
        if (orderedLibs != null) {
            // handle ordered libs directly, ...
            for (String lib : orderedLibs) {
                URL jarUrl = servletContext.getResource(LIB + lib);
                if (jarUrl == null) {
                    // should not happen, just ignore
                    continue;
                }

                String base = jarUrl.toExternalForm();
                URL url;
                if (base.endsWith("/")) {
                    url = new URL(base + configFile);
                } else {
                    url = JarFactory.getJarEntryURL(jarUrl, configFile);
                }
                try {
                    parseConfigFile(applicationServicesFound, url);
                } catch (FileNotFoundException e) {
                    // no provider file found, this is OK
                }
            }

            // and the parent ClassLoader for all others
            loader = context.getParentClassLoader();
        }

        Enumeration<URL> resources;
        if (loader == null) {
            resources = ClassLoader.getSystemResources(configFile);
        } else {
            resources = loader.getResources(configFile);
        }
        while (resources.hasMoreElements()) {
            parseConfigFile(containerServicesFound, resources.nextElement());
        }

        // Filter the discovered container SCIs if required
        if (containerSciFilterPattern != null) {
            Iterator<String> iter = containerServicesFound.iterator();
            while (iter.hasNext()) {
                if (containerSciFilterPattern.matcher(iter.next()).find()) {
                    iter.remove();
                }
            }
        }

        // Add the application services after the container services to ensure
        // that the container services are loaded first
        containerServicesFound.addAll(applicationServicesFound);

        // load the discovered services
        if (containerServicesFound.isEmpty()) {
            return Collections.emptyList();
        }
        
        // 此處加載 路徑下的文件,然後通過反射創建對象返回。
        return loadServices(serviceType, containerServicesFound);
    }

 

最終創建的  ServletContainerInitializer  放入到   ContextConfig中的屬性中:Map<ServletContainerInitializer, Set<Class<?>>> initializerClassMap = new LinkedHashMap<>(); 等待後續被調用。

大致的調用順序:

tomcat啓動時webConfig() 的調用鏈:
Tomcat.start()->各種代理的start()->org.apache.catalina.core.StandardContext.startInternal->LifecycleBase.fireLifecycleEvent->org.apache.catalina.startup.ContextConfig.lifecycleEvent->configureStart->webConfig->processServletContainerInitializers()->load(Class<T> serviceType);


上面的文章主要是說明Tomcat啓動時,用到的SPI機制。

下面我們學習一下,tomcat啓動時如何通過SPI和SpringMVC結合到一起的。以及啓動方式。

java的類加載機制,servlet3.0新特性,java的spi機制,以及spring-mvc的初始化和加載過程。

到這裏不得不說一下tomcat啓動項目的兩種方式: 

  1. web.xml方式。
  2. Servlet3.0後的註解形式。
     

SpringMVC初始化
  之前我使用spring和springMVC的時候都是在web.xml裏面定義一個  listener org.springframework.web.context.ContextLoaderListener 用來初始化spring和一個 servlet org.springframework.web.servlet.DispatcherServlet 用來初始化springMVC。

我看了下DispatcherServlet的源碼初始化流程,如下圖DispatcherServlet初始化的時候,會創建一個WebApplicationInitializer。左邊三者是一個繼承關係。

WebApplicationContext和ApplicationContext有什麼不同呢? 前者實現了後者,我們可以配置多個servlet對應不同的mapping,每個servlet會對應一個WebApplicationContext(wac),但是ApplicationContext(ac)只有一個,wac之間可以共享ac裏面配置的bean,如共享數據源,緩存等,而且如果不需要這些配置,ac也不是必須的。ac和wc是一個parent-child的層級關係。
 

我們看一下 web.xml:

<?xml version="1.0" encoding="UTF-8"?> 
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" 
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
     xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"> 

  <!-- 在Spring框架中是如何解決從頁面傳來的字符串的編碼問題的呢?
  下面我們來看看Spring框架給我們提供過濾器CharacterEncodingFilter 
   這個過濾器就是針對於每次瀏覽器請求進行過濾的,然後再其之上添加了父類沒有的功能即處理字符編碼。 
   其中encoding用來設置編碼格式,forceEncoding用來設置是否理會 request.getCharacterEncoding()方法,設置爲true則強制覆蓋之前的編碼格式。--> 
  <filter> 
    <filter-name>characterEncodingFilter</filter-name> 
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> 
    <init-param> 
      <param-name>encoding</param-name> 
      <param-value>UTF-8</param-value> 
    </init-param> 
    <init-param> 
      <param-name>forceEncoding</param-name> 
      <param-value>true</param-value> 
    </init-param> 
  </filter> 
  <filter-mapping> 
    <filter-name>characterEncodingFilter</filter-name> 
    <url-pattern>/*</url-pattern> 
  </filter-mapping> 
  <!-- 項目中使用Spring 時,applicationContext.xml配置文件中並沒有BeanFactory,要想在業務層中的class 文件中直接引用Spring容器管理的bean可通過以下方式--> 
  <!--1、在web.xml配置監聽器ContextLoaderListener--> 
  <!--ContextLoaderListener的作用就是啓動Web容器時,自動裝配ApplicationContext的配置信息。因爲它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啓動容器時,就會默認執行它實現的方法。 
  在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成。 
  它的API說明 
  第一段說明ContextLoader可以由 ContextLoaderListener和ContextLoaderServlet生成。 
  如果查看ContextLoaderServlet的API,可以看到它也關聯了ContextLoader這個類而且它實現了HttpServlet這個接口 
  第二段,ContextLoader創建的是 XmlWebApplicationContext這樣一個類,它實現的接口是WebApplicationContext->ConfigurableWebApplicationContext->ApplicationContext-> 
  BeanFactory這樣一來spring中的所有bean都由這個類來創建 
   IUploaddatafileManager uploadmanager = (IUploaddatafileManager)  ContextLoaderListener.getCurrentWebApplicationContext().getBean("uploadManager");
   --> 
  <listener> 
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
  </listener> 
  <!--2、部署applicationContext的xml文件--> 
  <!--如果在web.xml中不寫任何參數配置信息,默認的路徑是"/WEB-INF/applicationContext.xml, 
  在WEB-INF目錄下創建的xml文件的名稱必須是applicationContext.xml。 
  如果是要自定義文件名可以在web.xml里加入contextConfigLocation這個context參數: 
  在<param-value> </param-value>裏指定相應的xml文件名,如果有多個xml文件,可以寫在一起並以“,”號分隔。 
  也可以這樣applicationContext-*.xml採用通配符,比如這那個目錄下有applicationContext-ibatis-base.xml, 
  applicationContext-action.xml,applicationContext-ibatis-dao.xml等文件,都會一同被載入。 
  在ContextLoaderListener中關聯了ContextLoader這個類,所以整個加載配置過程由ContextLoader來完成。--> 
  <context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value>classpath:spring/applicationContext.xml</param-value> 
  </context-param> 

  <!--如果你的DispatcherServlet攔截"/",爲了實現REST風格,攔截了所有的請求,那麼同時對*.js,*.jpg等靜態文件的訪問也就被攔截了。--> 
  <!--方案一:激活Tomcat的defaultServlet來處理靜態文件--> 
  <!--要寫在DispatcherServlet的前面, 讓 defaultServlet先攔截請求,這樣請求就不會進入Spring了,我想性能是最好的吧。--> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.css</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.swf</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.gif</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.jpg</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.png</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.js</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.html</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.xml</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.json</url-pattern> 
  </servlet-mapping> 
  <servlet-mapping> 
    <servlet-name>default</servlet-name> 
    <url-pattern>*.map</url-pattern> 
  </servlet-mapping> 
  <!--使用Spring MVC,配置DispatcherServlet是第一步。DispatcherServlet是一個Servlet,,所以可以配置多個DispatcherServlet--> 
  <!--DispatcherServlet是前置控制器,配置在web.xml文件中的。攔截匹配的請求,Servlet攔截匹配規則要自已定義,把攔截下來的請求,依據某某規則分發到目標Controller(我們寫的Action)來處理。--> 
  <servlet> 
    <servlet-name>DispatcherServlet</servlet-name><!--在DispatcherServlet的初始化過程中,框架會在web應用的 WEB-INF文件夾下尋找名爲[servlet-name]-servlet.xml 的配置文件,生成文件中定義的bean。--> 
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
    <!--指明瞭配置文件的文件名,不使用默認配置文件名,而使用dispatcher-servlet.xml配置文件。--> 
    <init-param> 
      <param-name>contextConfigLocation</param-name> 
      <!--其中<param-value>**.xml</param-value> 這裏可以使用多種寫法--> 
      <!--1、不寫,使用默認值:/WEB-INF/<servlet-name>-servlet.xml--> 
      <!--2、<param-value>/WEB-INF/classes/dispatcher-servlet.xml</param-value>--> 
      <!--3、<param-value>classpath*:dispatcher-servlet.xml</param-value>--> 
      <!--4、多個值用逗號分隔--> 
      <param-value>classpath:spring/dispatcher-servlet.xml</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup><!--是啓動順序,讓這個Servlet隨Servletp容器一起啓動。--> 
  </servlet> 
  <servlet-mapping> 
    <!--這個Servlet的名字是dispatcher,可以有多個DispatcherServlet,是通過名字來區分的。每一個DispatcherServlet有自己的WebApplicationContext上下文對象。同時保存的ServletContext中和Request對象中.--> 
    <!--ApplicationContext是Spring的核心,Context我們通常解釋爲上下文環境,我想用“容器”來表述它更容易理解一些,ApplicationContext則是“應用的容器”了:P,Spring把Bean放在這個容器中,在需要的時候,用getBean方法取出--> 
    <servlet-name>DispatcherServlet</servlet-name> 
    <!--Servlet攔截匹配規則可以自已定義,當映射爲@RequestMapping("/user/add")時,爲例,攔截哪種URL合適?--> 
    <!--1、攔截*.do、*.htm, 例如:/user/add.do,這是最傳統的方式,最簡單也最實用。不會導致靜態文件(jpg,js,css)被攔截。--> 
    <!--2、攔截/,例如:/user/add,可以實現現在很流行的REST風格。很多互聯網類型的應用很喜歡這種風格的URL。弊端:會導致靜態文件(jpg,js,css)被攔截後不能正常顯示。 --> 
    <url-pattern>/</url-pattern> <!--會攔截URL中帶“/”的請求。--> 
  </servlet-mapping> 

  <welcome-file-list><!--指定歡迎頁面--> 
    <welcome-file>login.html</welcome-file> 
  </welcome-file-list> 
  <error-page> <!--當系統出現404錯誤,跳轉到頁面nopage.html--> 
    <error-code>404</error-code> 
    <location>/nopage.html</location> 
  </error-page> 
  <error-page> <!--當系統出現java.lang.NullPointerException,跳轉到頁面error.html--> 
    <exception-type>java.lang.NullPointerException</exception-type> 
    <location>/error.html</location> 
  </error-page> 
  <session-config><!--會話超時配置,單位分鐘--> 
    <session-timeout>360</session-timeout> 
  </session-config> 
</web-app>

1、spring 框架解決字符串編碼問題:過濾器 CharacterEncodingFilter(filter-name) 

2、在web.xml配置監聽器ContextLoaderListener(listener-class) 
ContextLoaderListener的作用就是啓動Web容器時,自動裝配ApplicationContext的配置信息。因爲它實現了ServletContextListener這個接口,在web.xml配置這個監聽器,啓動容器時,就會默認執行它實現的方法。 

3、部署applicationContext的xml文件:contextConfigLocation(context-param下的param-name) 

4、DispatcherServlet是前置控制器,配置在web.xml文件中的。攔截匹配的請求,Servlet攔截匹配規則要自已定義,把攔截下來的請求,依據某某規則分發到目標Controller(我們寫的Action)來處理。 

DispatcherServlet(servlet-name、servlet-class、init-param、param-name(contextConfigLocation)、param-value) 
在DispatcherServlet的初始化過程中,框架會在web應用的 WEB-INF文件夾下尋找名爲[servlet-name]-servlet.xml 的配置文件,生成文件中定義的bean

通過上面的瞭解,我們可以看出spring核心配置文件就是listener那塊。在監聽之前我們已經通過context-param將spring配置文件傳到上下文中了(application)。下面我們就來看看spring是如何工作的吧

第一步: 

點開listener源碼,我們發現他有下面幾個方法。和繼承的關係。我們發現他實現了ContextLoaderListener這個接口,這個接口在參數設置好之後自動執行contextInitialized方法的。

那麼我們來看看contextInitialized方法

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
      throw new IllegalStateException(
          "Cannot initialize context because there is already a root application context present - " +
          "check whether you have multiple ContextLoader* definitions in your web.xml!");
    }

    Log logger = LogFactory.getLog(ContextLoader.class);
    servletContext.log("Initializing Spring root WebApplicationContext");
    if (logger.isInfoEnabled()) {
      logger.info("Root WebApplicationContext: initialization started");
    }
    long startTime = System.currentTimeMillis();

    try {
      // Store context in local instance variable, to guarantee that
      // it is available on ServletContext shutdown.
      if (this.context == null) {
        this.context = createWebApplicationContext(servletContext);
      }
      if (this.context instanceof ConfigurableWebApplicationContext) {
        ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
        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 ->
            // determine parent for root web application context, if any.
            ApplicationContext parent = loadParentContext(servletContext);
            cwac.setParent(parent);
          }
          configureAndRefreshWebApplicationContext(cwac, servletContext);
        }
      }
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

      ClassLoader ccl = Thread.currentThread().getContextClassLoader();
      if (ccl == ContextLoader.class.getClassLoader()) {
        currentContext = this.context;
      }
      else if (ccl != null) {
        currentContextPerThread.put(ccl, this.context);
      }

      if (logger.isDebugEnabled()) {
        logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
            WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
      }
      if (logger.isInfoEnabled()) {
        long elapsedTime = System.currentTimeMillis() - startTime;
        logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
      }

      return this.context;
    }
    catch (RuntimeException ex) {
      logger.error("Context initialization failed", ex);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
      throw ex;
    }
    catch (Error err) {
      logger.error("Context initialization failed", err);
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
      throw err;
    }
  }

仔細研究官方解釋,就是在這裏初始化application,這裏會用到contextClass+contextConfigLocation兩個參數,如果contextClass在context-param提供了,我們就會根據這一個class去初始化application,很顯然我們正常配置都沒有配這個,而是配置了後者,配置了後者就會去根據contextConfigLocation中提供的配置文件去解析然後創建相關的bean和application操作,這個方法的最後會執行configureAndRefreshWebApplicationContext方法。這個方法就是在根據contextConfigLocation提供的配置文件中創建相關的bean。

springMVC 加載

springMVC其實和spring是一樣的,但是他不用再程序開始時訪問

  <servlet> 
    <!-- 配置DispatcherServlet --> 
    <servlet-name>springMvc</servlet-name> 
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 
      <!-- 指定spring mvc配置文件位置 不指定使用默認情況 --> 
      <init-param>   
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring/spring-mvc.xml</param-value>
      </init-param> 
    <!-- 設置啓動順序 --> 
    <load-on-startup>1</load-on-startup> 
  </servlet>

  <!-- ServLet 匹配映射 -->
  <servlet-mapping>
    <servlet-name>springMvc</servlet-name>
    <url-pattern>*.zxh</url-pattern>
  </servlet-mapping>

看DispatcherServlet源碼中對contextConfigLocation參數的解釋

上面明確指出我們這個參數給XmlWebApplicationContext類的,我們在進入XmlWebApplicationContext類看看究竟。

這樣我們很容易理解爲什麼springmvc默認的配置文件會在WEB-INF/application.xml中。

在dispatcherservlet中有一個初始化方法,這裏就初始化配置中一些東西,比如說文件上傳適配器的配置等

protected void initStrategies(ApplicationContext context) {
    initMultipartResolver(context);
    initLocaleResolver(context);
    initThemeResolver(context);
    initHandlerMappings(context);
    initHandlerAdapters(context);
    initHandlerExceptionResolvers(context);
    initRequestToViewNameTranslator(context);
    initViewResolvers(context);
    initFlashMapManager(context);
  }

 

Servlet3.0後的註解形式。

隨着servlet3.0的到來,web.xml也不是必須的了,我們可以定義一個WebApplicationInitializer來初始化一個WebApplicationContext,下面我講講WebApplicationInitializer的加載機制
看下面介紹的<a href="#ServletContext的性能增強"">servlet3對ServletContext的增強可以知道,javaee容器在啓動的時候會通過spi機制來尋找javax.servlet.ServletContainerInitializer的實現類,在spring-web jar包,如下圖所示
 

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

@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 {
                        initializers.add((WebApplicationInitializer) waiClass.newInstance());
                    }
...
}

SpringServletContainerInitializer 的onStartup()方法裏面有如下一段註釋

Because this class declares @HandlesTypes(WebApplicationInitializer.class), Servlet 3.0+ containers will automatically scan the classpath for implementations of Spring’s WebApplicationInitializer interface and provide the set of all such types to the webAppInitializerClasses parameter of this method.

因爲這個類@HandlesTypes註解的是WebApplicationInitializer.class,Servlet3.0容器會自動的掃描classpath下面WebApplicationInitializer接口的實現類,並提供給SpringServletContainerInitializer的onStartup()方法
 

spring-web中的具體應用
從servlet3.0開始,web容器啓動時爲提供給第三方組件機會做一些初始化的工作,例如註冊servlet或者filtes等,servlet規範中通過ServletContainerInitializer實現此功能。每個框架要使用ServletContainerInitializer就必須在對應的jar包的META-INF/services 目錄創建一個名爲javax.servlet.ServletContainerInitializer的文件,文件內容指定具體的ServletContainerInitializer實現類,那麼,當web容器啓動時就會運行這個初始化器做一些組件內的初始化工作。

一般伴隨着ServletContainerInitializer一起使用的還有HandlesTypes註解,通過HandlesTypes可以將感興趣的一些類注入到ServletContainerInitializerde的onStartup方法作爲參數傳入。
 

SpringServletContainerInitializer
通過源碼發現,配合註解@HandlesTypes它可以將其指定的Class對象作爲參數傳遞到onStartup方法中。進而在onStartup方法中獲取Class對象的具體實現類,進而調用實現類中的具體方法。SpringServletContainerInitializer類中@HandlesTypes指定的是Class對象是WebApplicationInitializer.Class。

利用這個機制,若實現WebApplicationInitializer這個接口,我們就可以自定義的注入Servlet,或者Filter,即可以不再依賴web.xml的配置。
 

 

Servlet3.0新特性


目前servlet4還正在開發過程中,目前最新的應該就是3了,最新的spring也用到了很多servlet3.0的特性,所以很有必要了解一下。
新特性概述:

  • 異步處理支持,之前是servlet一直阻塞直到業務完成,現在可以將耗時任務委派給另外一個線程處理,自己不生成相應的情況下返回到容器
  • 新增註解,簡化servlet、filter、listener,所以可以無需配置web.xml
  • 可插件支持,類似於開發應用時,將jar包放入classpath

異步處理支持


在web.xml配置<async-supported>true</async-supported>或者在註解裏面添加asyncSupported = true

新增註解支持


@WebServlet
聲明一個類爲servlet,註解在部署時被容器自動處理
WebInitParam
通常配合@WebServlet和@WebFilter使用,類似於web.xml裏面的<init-param>標籤,指定初始化參數
WebFilter
聲明一個類爲過濾器
WebListener
聲明一個類爲監聽器,該類必須實現如下最少一個接口

ServletContextListener
ServletContextAttributeListener
ServletRequestListener
ServletRequestAttributeListener
HttpSessionListener
HttpSessionAttributeListener
@MultipartConfig
該註解主要是爲了輔助 Servlet 3.0 中 HttpServletRequest 提供的對上傳文件的支持。

可插性支持


以配置servlet爲例,有三種方式

  1. 最原始的在web.xml裏面配置servlet
  2. 使用servlet3.0的@WebServlet註解
  3. 利用可插性,將類繼承HttpServlet,然後打成jar包,在jar包的META-INF裏面放置一個 web-fragment文件,在文件中聲明Servlet配置

我覺得spring可能就是利用了可插性,等有時間驗證一下。
 

ServletContext的性能增強

支持運行時,動態的部署servlet、過濾器、監聽器,通過ServletContext的方法實現。
ServletContainerInitializer 也是 Servlet 3.0 新增的一個接口,容器在啓動時使用SPI來發現 ServletContainerInitializer 的實現類,並且容器將 WEB-INF/lib 目錄下 JAR 包中的類都交給該類的 onStartup() 方法處理,我們通常需要在該實現類上使用 @HandlesTypes 註解來指定希望被處理的類,過濾掉不希望給 onStartup() 處理的類。
 

Implementations of this interface must be declared by a JAR file resource located inside the META-INF/services directory and named for the fully qualified class name of this interface
這個接口的實現必須打包在一個jar文件裏面,並且需要在META-INF/services/通過spi來定義

 

 

 

 

 

 

 

 

 

 

 

 

 


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章