Spring筆記

1.Spring整體架構

1)核心容器(Core Container)

  • Core模塊,主要包含了Spring框架基本的核心工具類,是其他組件的核心;
  • Beans模塊,主要包含了訪問配置文件、創建和管理Bean以及進行IoC/DI操作;
  • Context模塊,構建于Core和Beans上,ApplicationContext接口是關鍵;
  • Expression Language模塊,提供語言表達式用于在運行時查詢和操縱對象;

2)Data Access/Integration

  • JDBC模塊,包含了Spring對JDBC數據訪問進行封裝的所有類;
  • ORM模塊,對象-關系映射API,利用ORM封裝包,可以混合使用所有Spring提供的特性進行O/R映射;
  • OXM模塊,提供一個對 Object/XML 映射實現的抽象層;
  • JMS模塊,主要包含了一些制造和消費信息的特性;
  • Transaction模塊,支持編程和聲明性的事務管理;

3)Web
? ? ? ?Web上下文模塊建立在應用程序上下文模塊之上,為基于WEB的應用程序提供上下文

  • Web模塊,提供基礎的面向WEB的集成特性
  • Web-Servlet模塊,包含了Spring的model-view-controller(MVC)實現
  • Web-Struts模塊,提供了對Struts的支持
  • Web-Porlet模塊,提供了用于Porlet環境和Web-Servlet模塊的MVC實現

4)AOP
? ? ? ?提供了一個符合AOP聯盟標準的面向切面編程的實現,從而將邏輯代碼分開,降低它們之間的耦合性。

5)Test
? ? ? ?支持使用Junit和TestNG對Spring組件進行測試。

2.Bean的生命周期

BeanFactory中bean的生命周期

ApplicationContext中bean的生命周期

3.Bean的作用域

  • 單例(Singleton):(默認)整個應用中,只創建bean的一個實例;
  • 原型(Prototype):每次注入或者通過Spring應用上下文獲取的時候,都會創建一個新的bean的實例;
  • 會話(Session):在Web應用中,為每個會話創建一個bean實例;
  • 請求(Request):在Web應用中,為每個請求創建一個bean實例;

4.資源訪問(Resource)

資源抽象接口Resource在Spring框架中起著不可或缺的作用,Spring框架使用Resource裝載各種資源,包括配置文件資源、國際化屬性文件資源等。

  • WritableResource:可寫資源接口(Spring3.1增加接口),有兩個實現類,即FileSystemResource和PathResource(Spring 4.0提供)
  • ByteArrayResource:二進制數組表示的資源;
  • ClassPathResource:類路徑下的資源,資源以相對于類路徑的方式表示;
  • FileSystemResource:文件系統資源,以文件系統路徑的方式表示;
  • InputStreamResource:以輸入流返回表示的資源;
  • ServletContextResource:為訪問Web容器上下文的資源設計的類,負責以相對于Web應用根目錄的路徑加載資源;
  • UrlResource:封裝了java.net.URL,使用戶能夠訪問任何可以通過URL表示的資源;
  • PathResource:Spring4.0提供的讀取資源文件的新類,封裝了java.net.URL、java.nio.file.Path、文件系統資源,使用戶能夠訪問任何可以通過URL、Path、文件系統路徑表示的資源;

? ? ? ?為了訪問不同類型的資源,Resource能夠通過"classpath:"、"file"等資源地址前綴識別不同的資源類型,還支持Ant風格帶通配符的資源地址。
資源配置地址前綴

  • classpath:從類路徑加載資源,classpath:等價于classpath:/,資源文件可以在標準文件系統,也可以在JAR/ZIP的類包中,classpath*:匹配所有JAR包及類路徑;
  • file:使用UrlResource從文件系統目錄中裝載資源,可以使用絕對/相對路徑;
  • http://使用UrlResource從web服務器中裝載數據;
  • ftp://使用UrlResource從FTP服務器中裝載數據;
  • 無前綴,根據ApplicationContext的具體實現類采用對應類型的Resource;

Ant風格通配符

  • ?:匹配文件名中的一個字符;
  • *:匹配文件名中的任意字符;
  • **:匹配多層路徑;

? ? ? ?資源加載器ResourceLoader接口僅有一個getResource(String location)方法,僅支持通過帶資源類型前綴的資源地址加載文件資源;ResourcePatternResolver擴展了ResourceLoader,支持帶資源類型前綴和Ant風格的資源路徑表達式;PathMatchingResourcePatternResolver則是Spring標準實現類;

5.BeanFactory和ApplicationContext

1)BeanFactory
BeanFactory是一個類通用工廠,可以創建并管理各種類的對象,下圖是BeanFactory的繼承體系,主要是通過BeanFactory->HierarchicalBeanFactory->ConfigurableBeanFactory->...->XmlBeanFactory(廢棄):

  • ListableBeanFactory:接口定義了訪問容器中Bean基本信息的若干方法,如bean的個數、獲取某類bean的配置名、查看容器中是否包括某一個bean等
  • HierarchicalBeanFactory:父子級聯Ioc接口,子容器可以通過接口方法訪問父容器;
  • ConfigurableBeanFactory:增強了IoC容器的可定制性,定義了設置類裝載器、屬性編輯器、容器初始化后置處理器等方法;
  • AutowireCapableBeanFactory:定義了將容器中的bean按某種規則進行自動裝配的方法;
  • SingletonBeanRegistry:定義了允許在運行期向容器注冊單實例bean的方法;
  • BeanDefinitionRegistry:每個由<bean>定義的bean通過BeanDefinition對象表示,描述了配置信息。BeanDefinitionRegistry接口提供了向容器手工注冊BeanDefinition對象的方法;

? ? ? ?通過資源加載器ResourceLoader加載Resource資源,在初始化BeanDefinitionReader時指定要用的BeanFactory,使用Resource加載BeanDefinition并啟動IoC容器;然后就可以通過getBean方法獲取bean,bean的初始化發生在第一次調用時。Spring在DefaultSingletonBeanRegistry提供了一個用于緩存單例bean的緩存器,基于HashMap,單例bean以beanName為鍵保存在這個HashMap中。

2)ApplicationContext
? ? ? ?由BeanFactory派生而來,通過多個其他的接口擴展了BeanFactory的功能,提供了更多面向實際應用的功能,與BeanFactory不同的是,ApplicationContext在初始化應用上下文的時候就實例化所有單實例的bean。ApplicationContext的類繼承體系如下:

  • AppliactionEventPublisher:讓容器擁有發布應用上下文事件的功能,比如容器開啟/關閉事件,而實現了ApplicationListener事件監聽接口的bean可以接受到容器事件并處理;
  • MessageSource:為應用提供il8n國際化消息訪問功能;
  • ResourcePatternResolver:所有ApplicationContext實現類都實現了類似于PathMatchingResourcePatternResolver功能,可以通過帶前綴的Ant風格的資源文件裝載配置文件;
  • LifeCycle:提供了start()和stop()兩個方法,主要用于控制異步處理過程。ApplicationContext及其管理的bean同時實現該接口,ApplicationContext會將start/stop信息傳遞給容器中所有實現了該接口的bean,達到管理和控制JMX、任務調度等目的。
  • ConfigurableApplicationContext擴展于ApplicationContext,它新增了兩個主要方法close()和refresh(),讓ApplicationContext具有啟動、刷新和關閉應用上下文的能力。

3)WebApplicationContext
? ? ? ?專門為Web應用準備的,允許從相對于Web根目錄的路徑中裝載配置文件完成初始化工作,在非Web應用環境下,bean只有singleton和prototype兩種作用域,WebApplicationContext添加了3個新的作用域:request、session和global session;


? ? ? ?WebApplicationContext擴展了ApplicationContext,定義了一個常量ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE存放WebApplicationContext實例;
? ? ? ?ConfigurableWebApplication擴展了WebApplicationContext,允許通過配置的方式實例化WebApplicationContext:

  • setServletContext(ServletContext servletContext):為Spring設置web應用上下文;
  • setConfigLocations(String[] configLocations):設置Spring配置文件路徑,一般是相對于Web根目錄地址;

6.IOC

IoC(控制反轉)也被稱為DI(依賴注入),讓調用類對某一接口實現類的依賴關系由第三方(容器或協作類)注入,以移除調用類對某一接口實現類的依賴。
IoC主要可以劃分為3種類型:構造函數注入、屬性注入和接口注入。Spring支持構造函數注入和屬性注入。

  • 構造函數注入:通過調用類的構造函數,將接口實現類通過構造函數變量傳入;
  • 屬性注入:有選擇地通過set方法完成調用類所需依賴的注入;
  • 接口注入:將調用類所有依賴注入的方法抽象到一個接口中,調用類通過實現該接口提供相應的注入方法;

IOC容器的初始化過程:
? ? ? ?IoC容器的初始化由refresh()方法啟動,包括BeanDefinition的Resource定位、載入和注冊三個基本過程,這里僅僅是IoC容器的初始化過程,不包含依賴注入過程,

  • Resource定位,指BeanDefinition的資源定位(ClassPath、FileSystem、Url),Resource封裝了對配置文件的I/O操作;
  • BeanDefinition的載入,把用戶定義好的bean表示成IoC容器內部的數據結構,即BeanDefinition。通過調用XML解析器得到document對象,并在documentReader(DefaultBeanDefinitionDocumentReader)里對Spring的bean規則進行解析,通過BeanDefinitionParserDelegate(定義了對各種Spring Bean定義規則的處理)進行Document文檔樹解析得到BeanDefinitionHolder(封裝了BeanDefinition、 Bean的名字和別名)
  • 向IoC容器注冊BeanDefinition,通過調用BeanDefinitionRegistry接口的實現來完成,IoC容器將BeanDefinition注入到HashMap中,以持有這些數據,key為bean的名字,value為beanDefinition;

IOC的依賴注入

? ? ? ?初始化過程主要是在IoC容器中建立BeanDefinition數據映射,并沒有對Bean的依賴關系進行注入,依賴注入的過程是在用戶第一次向IoC容器索要bean時觸發的,當然也可以通過lazy-init屬性讓容器完成對bean的預實例化。

  • AbstractBeanFactory中實現了getBean(),觸發了依賴注入,并嘗試從當前工廠獲取單例模式下的bean;
  • 若當前工廠不存在該bean的單例實例時,遞歸的調用父類工廠的getBean()方法,直到存在此單例bean或對應的BeanDefinition;
  • 獲取該BeanDefinition,并遞歸的獲取當前的bean的所有依賴(實際是通過依賴bean的名,遞歸的調用getBean(dependsOnBean)對其進行注冊,即經歷了調用getBean()后當前bean經歷的所有流程),并注冊為依賴bean;
  • 判斷該bean是Singleton、Prototype還是作用域型,并調用相應的creatBean()方法進行bean的創建;
  • 在creantBean()中,若該bean實現了PostProcessor,則返回一個代理,否則調用doCreatBean()對bean進行創建;
  • doCreatBean()中主要是調用creatBeanInstance()完成bean的實例化。實例化方法主要有:工廠方法實例化或依據配置通過CGLib進行構造器實例化。在IoC容器中,初始化策略由SimpleInstantiationStrategy類提供2種方法,一種是通過BeanUtils,使用了JVM的反射功能;一種就是通過CGLIB;
  • bean實例化后,通過調用populateBean()進行依賴注入流程;依賴關系定義在解析得到的BeanDefinition中;
  • 如果bean實現了InstantiationAwareBeanPostProcessor,則調用postProcessorAfterInstantiation()方法對bean進行初始化后的處理;
  • 進行依賴注入的準備,先處理autowire的注入,再調用applePropertyValues()進行屬性注入,在屬性注入之前,如果bean實現了InstantiationAwareBeanPostProcessor,則調用其postProcessProperty()方法對bean進行注入前的相應處理;
  • 調用BeanDefinitionValueResolver對BeanDefinition進行參數解析。如果是一個引用,且對應bean在雙親IoC容器中,則到雙親IoC容器中去獲取(通過getBean(),若依賴bean沒有實例化和依賴注入,則觸發),否則在當前IoC容器種獲取bean;
  • 調用BeanDefinitionValueResolver中的resolveValueIfNecessary()方法對獲取得到的bean實例進行類型處理;
  • 解析完成后,已經為依賴注入準備好了所需條件,通過調用BeanWrapper中的setPropertyValues()進行真正的依賴注入,對不同類型的參數(Array、List、Map等),通過反射機制取得注入屬性的set方法,將對象注入進去;

Bean的初始化方法

  • 在調用bean的初始化方法之前,會調用一系列的aware接口實現(如果bean實現了該接口),把相關的BeanName、BeanClassLoader以及BeanFactory注入到bean中。如果實現了BeanPostProcessor,則調用postProcessorBeforeInitialization()。如果實現了InitializingBean接口,則調用相應的afterPropertiesSet()方法實現初始化處理;
  • 如果Bean配置了initMethod,則通過invokeCustomInitMethod()來調用,實際上是根據初始化方法名,然后通過反射機制調用;
  • 最后如果實現了BeanPostProcessor接口,則調用postProcessorAfterInitialization方法;

Bean的依賴檢查
? ? ? ?在一般情況下,Bean的依賴注入是在應用第一次向容器索取Bean的時候發生,不能保證注入一定能夠成功,如果需要重新檢查這些依賴關系的有效性,IoC容器中,設計了一個依賴檢查特性,在Bean定義中設置dependency-check屬性來指導依賴檢查模式。具體實現是在AbstractAutowireCapableBeanFactory實現creatBean的過程中完成的,會對Bean的Dependencies屬性進行檢查,如果不滿足要求則拋出異常。

7.AOP

? ? ? ?通過在代理類中包裹切面,核心是JDK動態代理,以動態代理為基礎,設計出一系列的AOP的橫切實現,比如前置通知、返回通知、異常通知等。Spring AOP需要為目標對象建立代理對象,可以通過JDK的Proxy或者第三方類生成器CGLIB來完成,然后啟動代理對象的攔截器來完成各種橫切面的織入。Spring在運行時通知對象,只支持方法連接點,不支持字段和構造器連接點

AOP基本術語:

  • 通知:定義了切面是什么以及何時使用,描述了切面要完成的工作以及何時執行此工作。切面可以應用5中類型的通知:前置通知、后置通知、返回通知、異常通知、環繞通知;
  • 連接點:在應用執行過程中能夠插入切面的一個點,可以是調用方法時、拋出異常時、修改一個字段時;
  • 切點:匹配通知所要織入的一個或多個連接點,即定義了在何處進行通知操作;
  • 切面:通知與切點的結合,定義了在何時何處完成何種功能;
  • 引入:運行我們向現有類添加新的方法或屬性;
  • 織入:將切面應用到目標對象并創建新的代理對象的過程,即切面在指定的連接點上被織入到目標對象中。可以在編譯期、類加載期或在運行期通過動態代理織入;

建立AopProxy代理對象
? ? ? ?在Spring的AOP模塊中,主要的部分是代理對象的生成,通過調用和配置Spring的ProxyFactoryBean來完成,該類封裝了主要代理對象的生成過程,可以使用JDK的Proxy和CGLIB兩種生成方式。

  • 定義使用的通知器Advisor,以Bean的形式。通知器的實現定義了需要對目標對象進行增強的切面行為;
  • 定義ProxyFactoryBean,封裝AOP功能的主要類,需要設定與AOP實現相關的重要屬性,proxyInterface,interceptorNamestarget;
  • 定義target屬性,作為target屬性注入的Bean,是需要AOP通知器中的切面應用來增強的對象;

1)ProxyFactoryBean生成AopProxy代理對象
? ? ? ?以getObject()方法作為入口,需要對target目標對象增加的增強處理,都通過getObject方法實現了封裝。首先對通知器鏈進行初始化,通知器鏈封裝了一系列的攔截器,在生成代理對象時,根據singleton類型和prototype類型的不同,還需要對代理對象的生成做一個區分;

  • 通知器鏈初始化:通知器鏈是在initializeAdvisorChain()中完成,有標志位advisorChainInitialized,表示通知器鏈已經初始化,也就是說通知器鏈只會在第一次通過ProxyFactoryBean去獲取代理對象的時候初始化
    • 通知器鏈的初始化會讀取配置中的所有通知器,區分了全局通知器Global(代理工廠必須繼承了ListableBeanFacoty)和局部的singleton、prototype類型;
    • 接著會將得到的通知器名字交給addAdvisorOnChainCreation()方法,調用IoC容器的getBean()方法獲取到通知器Bean加入到通知器鏈中;
  • 生成singleton代理對象:由getSingletonInstance()方法實現,是生成AopProxy代理對象的入口,代理對象會封裝對目標對象target的調用,針對target對象的方法調用行為會被這里生成的代理對象所攔截。
    • 首先讀取ProxyFactoryBean中的配置,為生成代理對象做準備,比如判斷目標類是否存在,設置代理對象的接口等;
    • 接著,使用AopProxyFactory創建AopProxy,使用的是DefaultAopProxyFactory,其作為生成AopProxy對象的生成工廠,如果目標對象是接口類的實現,則適合使用JdkDynamicAopProxy來生成代理對象,否則Spring會使用Cglib2AopProxy來生成目標對象的代理對象;

? ? ? ?AopProxy的接口設計很簡單,就是獲取Proxy代理對象,在此接口下設計了Cglib2AopProxyJdkDynamicAopProxy兩種代理對象的實現,獲取Proxy代理對象的方式有兩種,一種需要指定ClassLoader,另一種不需要。

  • JdkDynamicAopProxy中生成代理對象,使用JDK的Proxy類來生成代理對象。

    - 首先從advised對象中取得代理對象的代理接口配置;  
    - 然后調用Proxy類的`newProxyInstance()`方法,得到對應的Proxy代理對象。  
    

? ? ? ?在生成代理對象時,需要三個參數,類裝載器、代理接口、Proxy回調方法所在的對象,該對象需要實現InvocationHandler接口,而JdkDynamicAopProxy實現了InvocationHandler接口及其invoke()回調方法,所以可以直接使用。

  • Cglib2AopProxy中生成代理對象
    • 仍是首先從advised對象中取得在IOC容器中配置的target代理對象;
    • 然后獲取代理對象target的代理接口,創建并配置CGLIB的Enhancer,Enhancer對象就是CGLIB的主要操作類;
    • 設置Enhancer對象,包括設置代理接口,回調方法callback,其回調是由DynamicAdvisedInterceptor來完成,此對象的回調入口是intercept()方法;
    • 最后通過Enhancer生成代理對象。

2)Spring AOP攔截器調用的實現
? ? ? ?Spring AOP通過上述兩種不同的代理對象生成方法生成代理對象時,相關的攔截器已經配置到了代理對象中,攔截器在代理對象中起作用是通過對這些方法的回調來完成的。

  • JdkDynamicAopProxy的invoke攔截:當Proxy對象的代理方法被調用時,JdkDynamicAopProxy(實現了InvocationHandler接口)的invoke方法作為Proxy對象的回調函數被觸發,從而通過invoke的具體實現,來完成對目標對象方法調用的攔截或者說功能增強。在invoke方法中完成了對Proxy對象的代理設置,包括獲取目標對象、攔截器鏈,如果沒有設置攔截器,則直接調用target的相應方法;如果設置了攔截器,需先調用攔截器,通過生成ReflectiveMethodInvocation對象來完成對AOP功能實現的封裝;
  • Cglib2AopProxy的intercept攔截:其回調是在DynamicAdvisedInterceptor對象中的intercept方法實現的,與JdkDynamicAopProxy的回調實現非常相似,只是在Cglib2AopProxy的intercept通過構造CglibMethodInvocation對象來完成攔截器鏈的調用;

目標對象方法的調用:若未設置攔截器,則直接調用目標對象的方法,對于JdkDynamicAopProxy,通過AopUtils使用反射機制在invokeJoinpointUsingReflection()方法中實現,首先得到調用方法的反射對象,然后使用invoke啟動對方法反射對象的調用;對于Cglib2AopProxy,通過其MethodProxy對象來直接完成。

AOP攔截器鏈的調用:雖然可以使用JDK和CGLIB生成不同的AopProxy代理對象,從而構造不同的回調方法來啟動對攔截器鏈的調用,但對攔截器的調用都是在ReflectiveMethodInvocation中,通過proceed()方法逐個運行攔截器的攔截方法來實現的。不過在運行攔截器的攔截方法之前,會通過對代理方法完成一個匹配判斷來決定攔截器是否滿足切面增強的要求,即在切點中進行matches的匹配過程,如果滿足則從攔截器中獲取通知器,并啟動攔截器的invoke方法進行切面增強;如果不匹配則遞歸的調用proceed方法,直到所有攔截器都被運行過,然后直接調用目標對象的實現方法。

配置通知器:攔截器的配置是在代理對象的回調方法中,由advised對象(AdvisedSupport對象)完成的,通過調用其getInterceptorsAndDynamicInterceptionAdvice方法生成攔截器,并且為了提高效率還加入了緩存,即針對目標對象方法的攔截器鏈在生成后會被緩存,所以只需生成一次。在此方法中生成攔截器鏈的工作是由advisorChainFactory(DefaultAdvisorChainFactory對象)完成的,此對象實現了攔截器鏈的獲取,具體流程如下:

  • 首先設置一個List,長度由配置的通知器個數決定;
  • 然后,DefaultAdvisorChainFacoty會通過AdvisorAdapterRegistry來實現攔截器的注冊;
  • 利用AdvisorAdapterRegistry注冊器來對從ProxyFactoryBean配置中獲取的通知進行適配(判斷目標類是否與通知器適配),從而獲得相應的攔截器,并加入到前面設置好的List中;

? ? ? ?攔截器適配和注冊完成后,List中的攔截器會被JDK/CGLIB生成的代理對象的回調方法invoke/intercept取得,并啟動攔截器的invoke調用,最終觸發通知的切面增強;

Advice通知的實現
? ? ? ?在為AopProxy代理對象配置攔截器的實現中,有一個取得攔截器的配置過程,由DefaultAdvisorChainFactory實現,這個工廠類負責生成攔截器鏈,在它的getInterceptorsAndDynamicInterceptionAdvice方法中,有一個適配和注冊的過程,通過配置Spring預先設計好的攔截器,Spring加入了它對AOP實現的處理。

  • 首先,在DefaultAdvisorChainFactory實現中,構造了一個GlobalAdvisorAdapterRegistry的單例;
  • 然后,對配置的Advisor通知器進行逐個遍歷,這些通知器鏈都是配置在interceptorNames中的;
  • getInterceptorsAndDynamicInterceptionAdvice傳遞進來的advised參數對象中,可以方便地獲取到配置的通知器,然后由GlobalAdvisorAdapterRegistry提供的DefaultAdvisorApapterRegistry單例來完成攔截器的適配和注冊過程;
  • DefaultAdvisorApapterRegistry中,首先其具有一系列與advice通知相對應的adapter適配實現,通過調用adapter的support方法,來判斷取得的advice屬于什么類型的advice通知,從而根據不同的通知來注冊不同的AdviceInterceptor。而這些AdviceInterceptor早已設計好,為實現不同的advice功能提供服務;
    ? ? ? ?以MethodBeforeAdviceAdapter為例,其實現了supportAdvice方法,advice是否是MethodBeforeAdvice的實例;實現了getInterceptor方法,將advice通知對象封裝到MethodBeforeAdviceInterceptor對象中。由于切面增強是通過遍歷攔截器,調用其invoke方法完成,
    • 在MethodBeforeAdviceInterceptor中,invoke方法被觸發,會先調用advice的before方法,達到對目標的增強效果(在方法調用前完成通知),然后調用MethodInvocation的proceed方法實現目標對象相應方法的調用;

AspectJ的切點表達式

AspectJ指示器 描述
arg() 限制連接點匹配參數為知道類型的執行方法
@args() 限制連接點匹配參數由指定注解標注的執行方法
executuon() 用于匹配是連接點的執行方法
this() 限制連接點匹配AOP代理的bean引用為指定類型的類
target 限制連接點匹配目標對象為指定類型的類
@target() 限制連接點匹配特定的執行對象,這些對象對應的類具有指定類型的注解
within() 限制連接點匹配指定的類型
@within() 限制連接點匹配指定注解所標注的類型
@annotation 限制匹配帶有指定注解的連接點

8.Spring MVC

? ? ? ?在自動裝配中,不需要對Bean屬性做顯式的依賴關系聲明,只需要配好autowiring屬性,IoC容器會根據這個屬性的配置,使用反射自動查找屬性的類型或者名字,然后基于屬性的類型或名字來自動匹配IoC容器中的Bean,從而自動地完成依賴注入。

請求處理流程

  • 整個過程始于客戶端發出一個HTTP請求,Web應用服務器接受到這個請求。如果匹配DispatcherServlet的請求映射路徑(web.xml中指定),則Web容器將該請求轉交給DispatcherServlet;
  • DispatcherServlet接收到這個請求后,將根據請求信息(包括URL,HTTP方法,請求報頭,請求參數,Cookie等)及HandlerMapping的配置找到處理請求的處理器(Handler);
  • 當DispatcherServlet根據HandlerMapping得到對應請求的Handler后,通過HandlerAdapter對Handler進行封裝,再以統一的適配器接口調用Handler;
  • 處理器完成業務邏輯的處理后將返回一個ModelAndView給DispatcherServlet,ModelAndView包含了視圖邏輯名和模型數據信息;
  • ModelAndView中包含的是"邏輯視圖名"而非真實的視圖對象,DispatcherServlet借由ViewResolver完成邏輯視圖名到真實視圖對象的解析工作;
  • 當得到真實的視圖對象View后,DispatcherServlet就使用這個View對象對ModelAndView中的模型數據進行視圖渲染;
  • 最終客戶端得到的響應信息可能是HTML頁面、XML或JSON、甚至是一張圖片或一個PDF文檔等不同的媒體形式;

8.1 上下文在Web容器中的啟動

IoC容器啟動的基本過程
? ? ? ?IOC容器的啟動過程就是建立上下文的過程,是與ServletContext相伴而生的,同時也是IoC容器在Web應用環境中的具體表現之一。由ContextLoaderListener啟動的上下文為根上下文,還有一個與Web MVC相關的上下文用來保存控制器需要的MVC對象。
? ? ? ?在web.xml中配置的ContextLoaderListener,為在Web容器中建立IoC容器服務,其實現了ServletContextListener接口,提供了Servlet生命周期結合的回調,比如contextInitializedcontextDestroyed方法,在contextInitialized的實現方法中完成了WebApplicationContext的建立,具體的載入過程是由ContextLoaderListener交由ContextLoader來完成的,主要是完成在Web容器中建立起雙親IoC容器,以及生成相應的WebApplicationContext并將其初始化。

Web容器中的上下文設計
? ? ? ?在WebApplicationContext中,可以看到ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE常量,用來索引在ServletContext中存儲的根上下文,同時提供了getServletContext()方法來獲取當前Web容器的Servlet上下文環境。
? ? ? ?在啟動過程中,Spring會使用一個默認的WebApplicationContext實現作為IoC容器,即XmlWebApplicationContext,在其初始化過程中,Web容器中的IoC容器被建立起來,該過程與對IoC容器的初始化分析一樣,有loadBeanDefinition對BeanDefinition的載入,不過在Web環境中,對BeanDefinition的Resource有特別要求,默認的BeanDefinition的配置路徑為/WEB-INF/applicationContext.xml,以一個常量保存在XmlWebApplicationContext中。

ContextLoader的設計與實現
? ? ? ?Web應用程序啟動時載入IoC容器的功能是由配置的監聽器ContextLoaderListener完成,通過使用ContextLoader完成IoC容器,即WebApplicationContext的初始化。

  • 首先,從Servlet事件中得到ServletContext;
  • 然后,讀取配置在web.xml的各個相關屬性值;
  • 接著,ContextLoader會實例化WebApplicationContext,并完成其載入和初始化過程;

? ? ? ?此監聽器是啟動根IoC容器并把它載入到Web容器的地方,生成的根上下文被綁定到Web應用程序的ServletContext上,需要訪問根上下文可以從WebApplicationContextUtils類的靜態方法getWebApplicationContext中得到。
? ? ? ?ContextLoaderListener實現了ServletContextListener接口,該接口里的函數會結合Web容器的生命周期被調用,它會監聽ServletContext事件并作出響應。在服務器啟動時,ServletContextListener的contextInitialized被調用;在服務器將要關閉時,contextDestroyed方法被調用。
? ? ? ?在初始化回調中,創建了ContextLoader,并利用它來完成IoC容器的初始化。此根上下文會作為Web容器的唯一實例而存在,如果在初始化過程中,發現已經有根上下文被創建,則拋出異常提示創建失敗。根上下文的創建需要設置雙親上下文、ServletContext

  • 根據傳入的ServletContext決定使用什么樣的類在WEB容器中作為IoC容器,可以通過設置CONTEXT_CLASS_PARAM參數來指定,若未指定則使用默認的XmlWebApplicationContext;
  • 然后直接實例化IoC容器并設置它的雙親上下文、ServletContext以及配置文件的位置參數;
  • 最后,調用refresh方法啟動容器的初始化,也就是一般IoC容器的初始化過程;

8.2 Spring MVC的設計與實現

? ? ? ?在完成對ContextLoaderListener的初始化之后,Web容器開始初始化DispatcherServlet的過程,DispatcherServlet會建立自己的上下文來持有Spring MVC的Bean對象,在建立此IoC容器時,會從ServletContext中得到根上下文作為其雙親上下文。
? ? ? ?DispatcherServlet通過集成FrameworkServletHttpServletBean而繼承了HttpServlet,通過使用Servlet API來對HTTP請求進行響應,成為Spring MVC的前端處理器,同時成為MVC模塊與Web容器集成的處理前端,其工作大致可以分為兩個部分:

  • 初始化部分,由HttpServletBean的init方法對initServletBean()的調用來啟動,通過initWebApplicationContext()方法建立IoC容器,最終調用DispatcherServlet的initStrategies方法,在此方法中,DispatcherServlet對MVC模塊的其他部分進行了初始化,如handlerMapping、ViewResolver等;
  • 對HTTP請求進行響應,作為一個Servlet,Web容器回調用Servlet的doGet()doPost()方法,經過FrameworkServlet的processRequest()簡單處理后,會調用DispatcherServlet的doService()方法,在此方法中封裝的doDispatch()實現了MVC模式的主要功能;

DispatcherServlet的啟動和初始化

  • 在init方法中開始初始化,讀取配置在ServletContext中的Bean屬性參數,這些屬性參數設置在web.xml的Web容器初始化參數中,如PropertyValues和BeanWrapper;
  • 接著在initWebApplicationContext方法中,執行DispatcherServlet持有的IoC容器的初始化過程,建立一個新的上下文,并將其設置為根上下文的子上下文,這樣根上下文就可以被各個Servlet持有的上下文所共享(通過getBean向IoC容器獲取Bean時,會先到其雙親IoC容器中獲取);
    • 建立DispatchServlet的上下文,需要把根上下文作為參數傳遞給它;
    • 然后使用反射技術實例化上下文對象,根據默認配置,該上下文對象也是XmlWebApplicationContext對象
    • 實例化結束后,需要為此上下文設置基本配置,如其雙親上下文、ServletContext引用以及Bean定義配置的文件位置等;
    • 最后調用refresh方法初始化該容器;
  • 通過initWebApplicationContext方法中調用的onRefresh()方法啟動DispatchServlet中的initStrategies方法,來完成整個Spring MVC框架的初始化,包括MVC框架的實現元素,比如支持國際化的LocalResolver、支持request映射的HandlerMappings以及視圖生成的ViewResolver等的初始化。以HandlerMappings為例:
    • 根據detectAllHandlerMapping設定值,默認為true,即默認從所有IoC容器中獲取控制器,否則從當前IoC容器中獲取;
    • 若都獲取不到,則需要設定默認控制器,此默認值設置在DispatcherServlet.properties中;
  • 初始化的最后,把上述生成的IoC容器設置到Web容器的上下文中;

MVC處理HTTP分發請求

①HandlerMapping的配置與設計
? ? ? ?在初始化完成時,在上下文環境中已定義的所有HandlerMapping都已經被加載了,加載的handlerMappings被放在一個List中并被排序,存儲著HTTP請求對應的映射數據。List中每一個元素對應一個具體的handlerMapping的配置,一般每個handlerMapping可以持有一系列從URL請求到Controller的映射(使用Map)。通過這些HandlerMapping中定義的映射關系,即URL請求與控制器的對應關系,使Spring MVC應用可以根據HTTP請求確定對應的Controller。
? ? ? ?映射關系是通過接口類HandlerMapping封裝的,在HandlerMapping接口中定義getHandler()方法,通過此方法,可以獲得與HTTP請求對應的HandlerExecutionChain,其封裝了具體的Controller和Interceptor鏈,通過攔截器給handler對象提供功能增強,同時提供了攔截器鏈的維護,可以調用addInterceptor()方法為攔截器鏈增加攔截器。
? ? ? ?例如具體的SimpleUrlHandlerMapping為例,其根據URL映射的方式,注冊Handler和Interceptor,從而維護一個反應這種映射關系的handlerMap。注冊過程在容器對Bean進行依賴注入時發生,實際上是通過Bean的postProcessor來完成。
? ? ? ?主要是通過其基類AbstractUrlHandlerMapping定義的registerHandler(String urlPath,Object handler)方法,在處理過程中:

  • 如果使用Bean的名稱作為映射,則直接從容器中獲取此HTTP映射對應的Bean;
  • 否則,對不同的URL配置進行解析,如HTTP請求中配置成/(映射的controller設置為rootHandler)和通配符/*(映射的controller設置為defaultHandler)的URL以及正常URL,

②使用HandlerMapping完成請求的映射處理
? ? ? ?在AbstractHandlerMapping中啟動getHandler的調用,取得Handler的具體過程在getHandlerInternal(HttpServletRequest request)方法中實現,實現過程包括了從HTTP請求中得到URL,并根據URL到urlMapping中獲得handler。

③Spring MVC對HTTP請求的分發處理
? ? ? ?在DispatcherServlet中,對HTTP請求的處理是在doService方法中完成的,實際是其交由doDispatch來完成,包括準備ModelAndView,調用getHandler來響應HTTP請求,然后通過執行Handler的處理來得到返回的ModelAndView結果,最后把該對象交給相應的視圖對象去呈現。

  • 首先,根據請求調用getHandler方法得到handler;
    • 在獲取handler時,首先會在HttpRequest中取得緩存的handler,對應HTTP的HANDLER_EXECUTION_CHAIN_ATTRIBUTE屬性位置;
    • 如果獲取不到(未緩存),通過在DispatcherServlet中持有的handlerMapping來生成一個(對應上述②);
    • 遍歷所有的handlerMapping,直到找到一個需要的handler為止;
    • 最后返回的是一個HandlerExecutionChain對象,并在HttpRequest進行緩存;
  • 調用handler的攔截器,進行預處理;
  • 在執行handler之前,獲取響應的適配器HandlerAdapter,并檢查handler的合法性,即判斷此handler是否為Controller接口的實現類;
  • 通過調用HandlerAdapter的handle方法,實際觸發對Controller的handleRequest方法的調用進行請求處理,處理的結果封裝到ModelAndView對象中,為視圖提供展現數據;
  • 判斷視圖是否需要進行視圖名的翻譯和轉換,并調用攔截器進行后置處理;
  • 調用render()方法使用視圖對ModelAndView數據進行展現;
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,786評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,656評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,697評論 0 379
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,098評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,855評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,254評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,322評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,473評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,014評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,833評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,016評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,568評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,273評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,680評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,946評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,730評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,006評論 2 374

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,787評論 18 139
  • 1.1 spring IoC容器和beans的簡介 Spring 框架的最核心基礎的功能是IoC(控制反轉)容器,...
    simoscode閱讀 6,743評論 2 22
  • Spring Boot 參考指南 介紹 轉載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,899評論 6 342
  • 個人認為讓朋友上百度是非常失敗的結果,誰都知道百度上,花錢就說好,不花錢就沒個好,畢竟人家百度靠這個賺錢。通常我會...
    武曄閱讀 321評論 0 0
  • 從去年1月份開始,累計語音超過1000分鐘,打電話時間52分鐘,約合18個小時,加上聊天信息時間約8小時,合計26...
    眼睛去旅行閱讀 179評論 0 0