SpringMVC源碼分析8、零XML配置SpringMVC環境原理

在SpringBoot的項目中, 咱們能夠看到, web.xml、springmvc.xml、applicationContext.xml這樣的配置文件已 經不見了, 取而代之的是各類的註解, 註解開發給咱們帶來了不少的便利, 利用JavaConfig完成項目的配置會顯得更 加的"高端", 本篇文章主要分析的是無XML實現SpringMVC環境搭建的原理, 若是理解了這些原理, 那麼SpringBoot的 也就不會太神祕了java

Servlet3.0規範的引入

衆所周知, Tomcat等web容器是用來放置Servlet的, Tomcat完成對底層網絡的處理, 最後纔將請求打到Servlet中,
在Servlet3.0規範出來後, 引入了許多的新特性, 其中一個特性是這樣的, 咱們能夠在/META-INF/services文件夾
下建立一個名爲javax.servlet.ServletContainerInitializer的文件, 這個文件中能夠放置任何的
ServletContainerInitializer接口的實現類, ServletContainerInitializer接口是一個函數式接口, 有一個
onStartup方法, Servlet3.0規範中指定, 在web容器中啓動的過程當中, 會掃描整個項目中/META-INF/services文件
夾下名爲javax.servlet.ServletContainerInitializer的文件, 調用這個文件中全部
ServletContainerInitializer實現類的onStartup方法, 下面咱們來演示一下:

目錄結構:
    src
        main
            java
            resources
                META-INF
                    services
                        javax.servlet.ServletContainerInitializer

文件javax.servlet.ServletContainerInitializer內容:
    com.test.MyWebInit

MyWebInit類代碼:
    public class MyWebInit implements ServletContainerInitializer {
        @Override
        public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
            System.out.println( "startUp......" );
        }
    }

分析:
    啓動tomcat, 會發現控制檯輸出了: startUp......

咱們再來看看這個onStartup方法, 有一個Set<Class<?>>的變量, 以及一個ServletContext, 後者你們應該很熟悉
了, Servlet上下文, 咱們來介紹一下前者, 有這麼一個註解@HandlesTypes, Servlet3.0規範中指定, 當該註解標
注在ServletContainerInitializer上的時候, web容器啓動的過程當中就會將整個項目中該註解中指定的接口的全部
實現類的Class對象放入一個set中並傳入onStrartup方法中, 下面咱們來修改下上述的例子:

package com.test.startup;
public interface StartUpInterface {}
public class StartUpInterfaceImpl implements StartUpInterface {}

@HandlesTypes( StartUpInterface.class )
public class MyWebInit implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException {
        System.out.println( c );
    }
}

分析:
    啓動tomcat, 發現控制檯輸出了: [class com.test.startup.StartUpInterfaceImpl]

由上面的特性, 咱們就能夠了解到, web容器提供了擴展點, 在容器啓動的過程當中, 容許咱們作一些事情, 利用這個擴
展點, 咱們就能實現無xml對SpringMVC的環境進行配置了
複製代碼

實現無xml完成SpringMVC環境搭建

結合以前的DispatcherServlet初始化流程源碼分析, 咱們須要知道一個點, 以前咱們配置SpringMVC的環境的時候,
是在web.xml中配置了DispatcherServlet的映射關係, 而且利用監聽器註冊了一個Spring容器, 在xml中配置
DispatcherServlet的映射關係, 其實就是將DispatcherServlet這個Servlet加入到了Servlet上下文中而已, 因而
咱們聯想上面的例子, 貌似咱們能夠手動的建立DispatcherServlet對象, 而後加到ServletContext中???

其次, 沒有了springmvc.xml文件, 那麼xml文件中配置的annotation-driven以及component-scan這些功能就無法
啓用, 無法開啓註解配置以及配置掃描的包了

再一次回顧前面的DispatcherServlet初始化流程源碼分析的內容, DispatcherServlet繼承於FrameworkServlet,
FrameworkServlet重寫了GenericServlet的空參init方法, 在這個重寫的方法中完成了容器的初始化, 因而乎, 我
們再來回顧一下, 容器的獲取方式, 一般狀況下, 是在這個初始化的過程當中會自動建立web容器, 可是這個建立的web
容器xmlApplicationContext, 而且不方便咱們去控制其建立的過程, 可是在原來的源碼分析中, 筆者也提到了, 我
們能夠手動的往DispatcherServlet中set一個容器, 那就好辦了, 咱們往裏面set一個註解配置的容器, 而不是xml
配置的容器, 因而, 無xml配置的原理就出來了, 咱們先看一下代碼:
public class MyWebInit implements ServletContainerInitializer {
    @Override
    public void onStartup(Set<Class<?>> c, ServletContext ctx) {
        AnnotationConfigWebApplicationContext context 
                                                = new AnnotationConfigWebApplicationContext();
        context.register( SpringConfig.class );

        DispatcherServlet dispatcherServlet = new DispatcherServlet();
        dispatcherServlet.setApplicationContext( context );
        ServletRegistration.Dynamic registration 
                                = ctx.addServlet( "dispatcherServlet", dispatcherServlet );
        registration.addMapping( "/" );
        registration.setLoadOnStartup( 1 );
        // registration.setInitParameter();
    }
}

分析:
    <1> 手動建立一個註解配置的Web容器, 若是對Spring源碼有所瞭解, 那麼register方法應該不會陌生, 就是
        註冊一個配置類, 這個配置類的內容很簡單:
            @Configuration
            @ComponentScan( "com.test" )
            @EnableWebMvc
            public class SpringConfig {}

    <2> 咱們在獨立的Spring環境下, 建立Spring環境是這樣的:
            AnnotationConfigApplicationContext context 
                                = new AnnotationConfigApplicationContext(SpringConfig.class);
        能夠在構造器中放入一個配置類, 構造器被執行的時候, 會調用register方法將SpringConfig配置類註冊
        到容器中, 同時調用refresh方法開始完成容器的初始化, 在web環境下的容器, 沒有提供一個構造器能夠
        傳遞配置類的, 因而咱們才手動調用register方法來註冊一個配置類, 注意了, 咱們沒有調用refresh方法
        以後咱們再來分析下緣由
    <3> 手動建立一個DispatcherServlet, 同時將建立好的web容器設置到DispatcherServlet中, 以後
        DispatcherServlet在初始化的時候就不會本身建立了, 而是採用咱們傳入的這個
    <4> 將DispatcherServlet添加到Servlet上下文中, 就等價於在web.xml中配置了一個DispatcherServlet,
        而後利用返回值registration(ServletRegistration.Dynamic類型的)添加這個Servlet須要攔截的請求,
        最後兩行就等價於在web.xml中的配置:
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:spring-mvc.xml</param-value>
            </init-param>
            <load-on-startup>1</load-on-startup>

到此爲止, 咱們利用Java代碼完成了在web.xml中配置的DispatcherServlet的功能, 與此同時, 整合了註解配置的
web容器與DispatcherServlet, 再來講下爲何咱們不主動調用refresh方法刷新容器, 回顧一下原來的
DispatcherServlet初始化流程源碼分析, 原來的web容器是在FrameworkServlet的空參init方法被觸發建立的, 由
於咱們手動放入了web容器, 因此就不會手動建立, 你們能夠回顧下以前的文章內容, 筆者着重的講解了在容器建立後
或者從DisatcherServlet中取到了設置進去的容器後, 會調用一個configureAndRefreshWebApplicationContext
方法, 在這個方法中的最後有這麼一行代碼:
    wac.refresh();

因而乎, 咱們知道了, 在FrameworkServlet中就會幫咱們主動調用refresh方法, 若是在此時咱們先調用的話, 那麼
可能會有些問題, 由於在configureAndRefreshWebApplicationContext方法中, 調用refresh方法以前, 會對容器
進行必定的設置, 好比說環境、監聽器(用於觸發SpringMVC的九大策略的初始化)等, 因此咱們不能在這個時候去調用
複製代碼

小小的總結

實現無xml完成的SpringMVC環境搭建的原理其實很簡單, 就是利用了Servlet3.0規範提供的
ServletContainerInitializer接口來完成的, 由於這個接口會傳入一個ServletContext, 因此咱們能夠直接對
ServletContext進行操做, 放入DispatcherServlet, 同時利用註解配置的上下文完成Spring環境的引入, 由此我
們已經能夠直接將各類的xml配置文件刪除了, 若是是Spring或者SpringMVC的配置, 能夠利用上面的SpringConfig
這個類來完成, 而若是是web.xml的配置, 能夠利用MyWebInit類中onStartUp方法來完成
複製代碼

內嵌tomcat容器

在咱們以前的分析中, 已經完成了無xml配置SpringMVC的環境了, 這裏作一個擴展, 咱們知道SpringBoot是內嵌
tomcat的容器的, 咱們也能夠完成這樣的壯舉!!

引入tomcat的jar包:
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-core</artifactId>
      <version>8.5.31</version>
    </dependency>

    <!-- 引入JSP支持 -->
    <dependency>
      <groupId>org.apache.tomcat.embed</groupId>
      <artifactId>tomcat-embed-jasper</artifactId>
      <version>8.5.31</version>
    </dependency>

建立啓動類:
    public class MyApplicationStarter {
        public static void main(String[] args) throws Exception{
            Tomcat tomcat = new Tomcat();
            tomcat.setPort(80);
            tomcat.addWebapp("/","D:\Users\Desktop\testProject\springmvc\src\main\webapp");
            context.addLifecycleListener(
                (LifecycleListener) Class.forName(tomcat.getHost().getConfigClass())
                .newInstance());

            tomcat.start();
            tomcat.getServer().await();
        }
    }

再結合以前的代碼, 咱們就實現了內嵌的tomcat應用, 從而實現了相似於SpringBoot的效果!!!
複製代碼

擴展: WebApplicationInitializer

最開始分析的Servlet3.0規範的時候, 咱們發現利用3.0規範, 咱們可以在tomcat容器啓動的過程當中進行插手, 只需
要在resources目錄下創建/META-INF/services/javax.servlet.ServletContainerInitializer的文件就行了,
tomcat容器啓動的時候會反射加載這個文件中的全部類, 並引入了@HandlesTypes註解, 在該註解標註的接口, 其所
有的子類會被傳入onStartup方法中, 然而, 上面是咱們本身實現的, 其實在SpringMVC中早就已經有了實現

SpringServletContainerInitializer是Spring對Servlet3.0規範的實現, 同時在spring-web這個包中, 就有一個
/META-INF/services/javax.servlet.ServletContainerInitializer文件, 在這個文件中就定義了這個
SpringServletContainerInitializer類, 這個類被@HandlesTypes註解標註了, 標註的接口是
WebApplicationInitializer接口, SpringServletContainerInitializer類的onStartUp方法完成的功能就是
從set中取出一個個WebApplicationInitializer接口的實現類, 建立對象, 並調用這些這些實現類的方法, 因此我
們只須要在程序中建立一個WebApplicationInitializer實現類就能夠完成以前咱們的全部功能了....
複製代碼
相關文章
相關標籤/搜索