❶ Spring源碼解析(一)- 容器的基本實現
Spring使用 基本的javaBean 來完成以前只可能由EJB完成的事情,是個分層架構。Spring創建bean都需要通過 讀取 、 解析 、 校驗配置文件, 然後注冊創建成Bean。 Spring是一個Bean容器 , 主要作用是替我們管理bean對象 (簡單的Java類對象的生命周期)。不管框架如何強大,還是需要我們程序員來告訴其一些必要信息的(比如要 管理的bean對象的類相關信息、是否開啟組件掃檔納描 等),這些我們稱之為對 Spring框架的配置 ,目前主流的配置方式是 通過使用配置文件或註解。
Spring中最核心的兩個源雀類: DefaultListableBeanFactory、XmlBeanDifinitionReader。DefaultListableBeanFactory 是整個bean載入的核心部分,是Spring注冊及載入bean的默認實現 。XmlBeanDefinitionReader 主要使用reader屬性對資源文件進行讀取和注冊。
XML配置文件讀取是Spring中重要的功能,大部分Spring大部分功能都是 以配置作為切入點 。 XmlBeanFactory 繼承自 DefaultListableBeanFactory ,而對於 DefaultListableBeanFactory 不同的地方其實是在 XmlBeanFactory 中使用了自定義的XML讀取器 XmlBeanDefinitionReader ,主要用於從XML文檔中讀取 BeanDefinition, 實現了個性化的 BeanDefinitionReader 讀取, DefaultListableBeanFactory 繼承了 並實現了 以及 BeanDefinitionRegistry 介面。
Spring的配置文件讀取是通過ClasaPathResource進行封裝的 ,如:new ClassPathResource("bean.xml")。在java中, 將不同來源的資源的讀取邏輯抽象成URL ,通過注冊不同的 handler來處理。 一般handler的類型使用不同的前綴,URL沒有默認定義相對的path路徑,也 沒有提供相關方法對資源進行檢查 ,顧Spring對其內部需要使用到的資源做了屬於自己的抽象結構, 用Resource介面來封裝底層資源。
Resource 介面繼承 InputStreamSource(封裝了任何能返回InputStream的類)。
Resource介面抽象了所有Spring內部使用到的底層資源 ,首先它定義了3個能判斷當前資源狀態的方法: 存在性(exists)、可讀性(isReadable)、是否處於打開狀態(isOpen) 。有了Resource介面便可以對所有資源進行統一處理。 ClassPathResource 中的實現是通過class或 classLoader 提供的底層方法進行調用。以此完成對配置文件資源的封裝。
當通過Resource相關類完成了對配置文件進行封裝,接下來由 XmlBeanDefinitionReader 完成對配置文件的讀取工作。雹蠢早
XML文件的驗證模式有兩種:DTD、XSD(XML Schema).
DTD即文檔類型定義, 是一種XML約束模式語言,是XML文件的驗證機制 。是一種保證XML文檔格式正確的有效方法, 可以通過比較XML文檔和DTD文件來查看文檔是否符合規范,元素和標簽的使用是否正確 。一個DTD文檔包含:元素的定義規則、元素間關系的定義規則、元素可使用的屬性、可使用的實體或符號規則。 要使用DTD驗證模式需要在XML文件的頭部聲明。
XML Schema語言就是XSD。 XML Schema描述了XML文檔的結構。可以用一個指定的XML Schema來驗證某個XML文檔,以檢查該XML文檔是否符合其要求。也可以 通過XML Schema指定一個XML文檔所允許的結構和內容 。XML Schema本身也是一個XML文檔,符合XML語法結構,可以用通用的XML解析器解析它。
使用XML Schema文檔對XML實例進行校驗,要聲明名稱空間和指定該名稱空間所對應的XML Schema文檔存儲位置 。通過schemaLocation屬性來指定名稱空間所對應的XML Schema文檔的存儲地址(1、名稱空間URL;2、該名稱空間所標識的XML Schema文件地址或URL地址)。
另外驗證模式通過 XmlBeanDefinitionReader 中的setValidationMode方法進行設定。而 Spring 用來檢測驗證模式的方法實際上就是判斷是否包含 DOCTYPE ,如果包含就是 DTD ,否則就是 XSD 。
XML文件經過驗證模式,交由DocumentLoader進行解析成對應的 Document。 而解析的過程中存在這么一環節:(EntityResolver) 根據聲明去尋找對應的DTD定義,以便對文檔進行驗證認證 。也可以通過setEntityResolver設置DTD定義。EntityResolver它用來接收兩個參數publicId和systemId,xsd格式文件通常publicId為null。而對於不同的驗證模式採用不同的解析器進行解析,並把文件轉換成Document文件,用於提取及注冊bean。
Document 文件通過 BeanDefinitionDocumentReader 進行內部邏輯處理,並提取root用於作為參數繼續完成BeanDefinition的注冊。
❷ Spring Tx源碼解析(二)
上一篇 我們介紹了 spring-tx 中的底層抽象,本篇我們一起來看看圍繞這些抽象概念 spring-tx 是如何打造出聲明式事務的吧。籠統的說, spring-tx-5.2.6.RELEASE 的實現主要分為兩個部分:
這兩部分彼此獨立又相互成就,並且每個部分都有著大量的源碼支撐,本篇我們先來分析 spring-tx 中的AOP部分吧。
EnableTransactionManagement 註解想必大家都很熟悉了,它是啟用 Spring 中注釋驅動的事務管理功能的關鍵。
EnableTransactionManagement 註解的主要作用是向容器中導入 ,至於註解中定義的幾個屬性在 Spring AOP源碼解析 中有過詳細分析,這里就不再贅述了。
由於我們並沒有使用 AspectJ ,因此導入容器的自然是 這個配置類。
這個配置類的核心是向容器中導入一個類型為 的Bean。這是一個 PointcutAdvisor ,它的 Pointcut 是 , Advice 是 TransactionInterceptor 。
利用 TransactionAttributeSource 解析 @Transactional 註解的能力來選取標注了 @Transactional 註解的方法,而 TransactionInterceptor 則根據應用提出的需求(來自對 @Transactional 註解的解析)將方法增強為事務方法,因此 可以識別出那些標注了 @Transactional 註解的方法,為它們應用上事務相關功能。
TransactionInterceptor 能對方法進行增強,但是它卻不知道該如何增強,比如是為方法新開一個獨立事務還是沿用已有的事務?什麼情況下需要回滾,什麼情況下不需要?必須有一個『人』告訴它該如何增強,這個『人』便是 TransactionAttributeSource 。
@Transactional 註解定義了事務的基礎信息,它表達了應用程序期望的事務形態。 TransactionAttributeSource 的主要作用就是解析 @Transactional 註解,提取其屬性,包裝成 TransactionAttribute ,這樣 TransactionInterceptor 的增強便有了依據。
前面我們已經見過, spring-tx 使用 來做具體的解析工作,其父類 定義了解析 TransactionAttribute 的優先順序,核心方法是 computeTransactionAttribute(...) 。
默認只解析 public 修飾的方法,這也是導致 @Transactional 註解失效的一個原因,除此之外它還實現了父類中定義的兩個模板方法:
同時為了支持 EJB 中定義的 javax.ejb.TransactionAttribute 和 JTA 中定義的 javax.transaction.Transactional 註解, 選擇將實際的提取工作代理給 TransactionAnnotationParser 。Spring 提供的 @Transactional 註解由 進行解析。
的源碼還是很簡單的,它使用 AnnotatedElementUtils 工具類定義的 find 語義來獲取 @Transactional 註解信息。 RuleBasedTransactionAttribute 中 rollbackOn(...) 的實現還是挺有意思的,其它的都平平無奇。
RollbackRuleAttribute 是用來確定在發生特定類型的異常(或其子類)時是否應該回滾,而 NoRollbackRuleAttribute 繼承自 RollbackRuleAttribute ,但表達的是相反的含義。 RollbackRuleAttribute 持有某個異常的名稱,通過 getDepth(Throwable ex) 演算法來計算指定的 Throwable 和持有的異常在繼承鏈上的距離。
程序猿只有在拿到需求以後才能開工, TransactionInterceptor 也一樣,有了 TransactionAttributeSource 之後就可以有依據的增強了。觀察類圖, TransactionInterceptor 實現了 MethodInterceptor 介面,那麼自然要實現介面中的方法:
可以看到, TransactionInterceptor 本身是沒有實現任何邏輯的,它更像一個適配器。這樣分層以後, TransactionAspectSupport 理論上就可以支持任意類型的 Advice 而不只是 MethodInterceptor 。實現上 TransactionAspectSupport 確實也考慮了這一點,我們馬上就會看到。
invokeWithinTransaction(...) 的流程還是非常清晰的:
第一步前文已經分析過了,我們來看第二步。
TransactionInfo 是一個非常簡單的類,我們就不費什麼筆墨去分析它了。接著看第三步,這一步涉及到兩個不同的操作——提交或回滾。
至此, TransactionInterceptor 於我們而言已經沒有任何秘密了。
本篇我們一起分析了 spring-tx 是如何通過 spring-aop 的攔截器將普通方法增強為事務方法的,下篇就該說道說道 PlatformTransactionManager 抽象下的事務管理細節啦,我們下篇再見~~
❸ [Spring boot源碼解析] 2 啟動流程分析
在了解 Spring Boot 的啟動流程的時候,我們先看一下一個Spring Boot 應用是如何啟動的,如下是一個簡單的 SpringBoot 程序,非常的簡潔,他是如何做到的呢,我們接下來就將一步步分解。
我們追蹤 SpringApplication.run() 方法,其實最終它主要的邏輯是新建一個 SpringApplication ,然後調用他的 run 方法,如下:
我們先來看一下創建 SpringApplication 的方法:
在將Main class 設置 primarySources 後,調用了 WebApplicationType.deceFromClasspath() 方法,該方法是為了檢查當前的應用類型,並設置給 webApplicationType 。 我們進入 deceFromClasspath 方法 :
這里主要是通過類載入器判斷是否存在 REACTIVE 相關的類信息,假如有就代表是一個 REACTIVE 的應用,假如不是就檢查是否存在 Servelt 和 ,假如都沒有,就代表應用為非 WEB 類應用,返回 NONE ,默認返回 SERVLET 類型,我們這期以我們目前最常使用的 SERVLET 類型進行講解,所以我們在應用中引入了 spring-boot-starter-web 作為依賴:
他會包含 Spring-mvc 的依賴,所以就包含了內嵌 tomcat 中的 Servlet 和 Spring-web 中的 ,因此返回了 SERVLET 類型。
回到剛才創建 SpringApplication 的構建方法中,我們設置完成應用類型後,就尋找所有的 Initializer 實現類,並設置到 SpringApplication 的 Initializers 中,這里先說一下 getSpringFactoriesInstances 方法,我們知道在我們使用 SpringBoot 程序中,會經常在 META-INF/spring.factories 目錄下看到一些 EnableAutoConfiguration ,來出發 config 類注入到容器中,我們知道一般一個 config 類要想被 SpringBoot 掃描到需要使用 @CompnentScan 來掃描具體的路徑,對於 jar 包來說這無疑是非常不方便的,所以 SpringBoot 提供了另外一種方式來實現,就是使用 spring.factories ,比如下面這個,我們從 Springboot-test 中找到的例子,這里先定義了一個ExampleAutoConfiguration,並加上了 Configuration 註解:
然後在 spring.factories 中定義如下:
那這種方式是怎麼實現的你,這就要回到我們剛才的方法 getSpringFactoriesInstances :
我們先來看一下傳入參數,這里需要注意的是 args,這個是初始化對應 type 的時候傳入的構造參數,我們先看一下 SpringFactoriesLoader#loadFactoryNames 方法:
首先是會先檢查緩存,假如緩存中存在就直接返回,假如沒有就調用 classLoader#getResources 方法,傳入 META-INF/spring.factories ,即獲取所有 jar 包下的對應文件,並封裝成 UrlResource ,然後使用 PropertiesLoaderUtils 將這些信息讀取成一個對一對的 properties,我們觀察一下 spring.factories 都是按 properties 格式排版的,假如有多個就用逗號隔開,所以這里還需要將逗號的多個類分隔開來,並加到 result 中,由於 result 是一個 LinkedMultiValueMap 類型,支持多個值插入,最後放回緩存中。最終完成載入 META-INF/spring.factories 中的配置,如下:
我們可以看一下我們找到的 initializer 有多少個:
在獲取到所有的 Initializer 後接下來是調用 方法進行初始化。
這里的 names 就是我們上面通過類載入器載入到的類名,到這里會先通過反射生成 class 對象,然後判斷該類是否繼承與 ApplicationContextInitializer ,最後通過發射的方式獲取這個類的構造方法,並調用該構造方法,傳入已經定義好的構造參數,對於 ApplicationContextInitializer 是無參的構造方法,然後初始化實例並返回,回到原來的方法,這里會先對所有的 ApplicationContextInitializer 進行排序,調用 #sort(instances) 方法,這里就是根據 @Order 中的順序進行排序。
接下來是設置 ApplicationListener ,我們跟進去就會發現這里和上面獲取 ApplicationContextInitializer 的方法如出一轍,最終會載入到如圖的 15 個 listener (這里除了 外,其他都是 SpringBoot 內部的 Listener):
在完成 SpringApplication 對象的初始化後,我們進入了他的 run 方法,這個方法幾乎涵蓋了 SpringBoot 生命周期的所有內容,主要分為九個步驟,每一個步驟這里都使用註解進行標識:
主要步驟如下:
第一步:獲取 SpringApplicationRunListener, 然後調用他的 staring 方法啟動監聽器。
第二步:根據 SpringApplicationRunListeners以及參數來准備環境。
第三步:創建 Spring 容器。
第四步:Spring 容器的前置處理。
第五步:刷新 Spring 容器。
第六步: Spring 容器的後置處理器。
第七步:通知所有 listener 結束啟動。
第八步:調用所有 runner 的 run 方法。
第九步:通知所有 listener running 事件。
我們接下來一一講解這些內容。
我們首先看一下第一步,獲取 SpringApplicationRunListener :
這里和上面獲取 initializer 和 listener 的方式基本一致,都是通過 getSpringFactoriesInstances , 最終只找到一個類就是: org.springframework.boot.context.event.EventPublishingRunListener ,然後調用其構造方法並傳入產生 args , 和 SpringApplication 本身:
我們先看一下構造函數,首先將我們獲取到的 ApplicationListener 集合添加到initialMulticaster 中, 最後都是通過操作 來進行廣播,我,他繼承於 ,我們先看一下他的 addApplicationListener 方法:
我們可以看出,最後是放到了 applicationListenters 這個容器中。他是 defaultRetriever 的成員屬性, defaultRetriever 則是 的私有類,我們簡單看一下這個類:
我們只需要看一下這里的 getApplicationListeners 方法,它主要是到 beanFactory 中檢查是否存在多的 ApplicationListener 和舊的 applicationListeners 組合並返回,接著執行 listener 的 start 方法,最後也是調用了 的 multicastEvent 查找支持對應的 ApplicationEvent 類型的通知的 ApplicationListener 的 onApplicationEvent 方法 ,這里除了會:
篩選的方法如下,都是調用了對應類型的 supportsEventType 方法 :
如圖,我們可以看到對 org.springframework.boot.context.event.ApplicationStartingEvent 感興趣的有5個 Listener
環境准備的具體方法如下:
首先是調用 getOrCreateEnvironment 方法來創建 environment ,我們跟進去可以發現這里是根據我們上面設置的環境的類型來進行選擇的,當前環境會創建 StandardServletEnvironment
我們先來看一下 StandardServletEnvironment 的類繼承關系圖,我們可以看出他是繼承了 AbstractEnvironment :
他會調用子類的 customizePropertySources 方法實現,首先是 StandardServletEnvironment 的實現如下,他會添加 servletConfigInitParams , servletContextInitParams , jndiProperties 三種 properties,當前調試環境沒有配置 jndi properties,所以這里不會添加。接著調用父類的 customizePropertySources 方法,即調用到了 StandardEnvironment 。
我們看一下 StandardEnvironment#customizePropertySources 方法,與上面的三個 properties 創建不同,這兩個是會進行賦值的,包括系統環境變數放入 systemEnvironment 中,jvm 先關參數放到 systemProperties 中:
這里會添加 systemEnvironment 和 systemProperties 這兩個 properties,最終拿到的 properties 數量如下 4個:
在創建完成 Environment 後,接下來就到了調用 configureEnvironment 方法:
我們先看一下 configurePropertySources 方法,這里主要分兩部分,首先是查詢當前是否存在 defaultProperties ,假如不為空就會添加到 environment 的 propertySources 中,接著是處理命令行參數,將命令行參數作為一個 CompositePropertySource 或則 添加到 environment 的 propertySources 裡面,
接著調用 ConfigurationPropertySources#attach 方法,他會先去 environment 中查找 configurationProperties , 假如尋找到了,先檢查 configurationProperties 和當前 environment 是否匹配,假如不相等,就先去除,最後添加 configurationProperties 並將其 sources 屬性設置進去。
回到我們的 prepareEnvironment 邏輯,下一步是通知觀察者,發送 事件,調用的是 SpringApplicationRunListeners#environmentPrepared 方法,最終回到了 #multicastEvent 方法,我們通過 debug 找到最後對這個時間感興趣的 Listener 如下:
其主要邏輯如下:
這個方法最後載入了 PropertySourceLoader , 這里主要是兩種,一個是用於 Properties 的,一個是用於 YAML 的如下:
其中 apply 方法主要是載入 defaultProperties ,假如已經存在,就進行替換,而替換的目標 PropertySource 就是 load 這里最後的一個 consumer 函數載入出來的,這里列一下主要做的事情:
1、載入系統中設置的所有的 Profile 。
2、遍歷所有的 Profile ,假如是默認的 Profile , 就將這個 Profile 加到 environment 中。
3、調用load 方法,載入配置,我們深入看一下這個方法:
他會先調用 getSearchLocations 方法,載入所有的需要載入的路徑,最終有如下路徑:
其核心方法是遍歷所有的 propertySourceLoader ,也就是上面載入到兩種 propertySourceLoader ,最紅 loadForFileExtension 方法,載入配置文件,這里就不展開分析了,說一下主要的作用,因為每個 propertySourceLoader 都有自己可以載入的擴展名,默認擴展名有如下四個 properties, xml, yml, yaml,所以最終拿到文件名字,然後通過 - 拼接所有的真實的名字,然後加上路徑一起載入。
接下來,我們分析 BackgroundPreinitializer ,這個方法在接收 ApplicationPrepareEnvironment 事件的時候真正調用了這份方法:
1、 ConversionServiceInitializer 主要負責將包括 日期,貨幣等一些默認的轉換器注冊到 formatterRegistry 中。
2、 ValidationInitializer 創建 validation 的匹配器。
3、 MessageConverterInitializer 主要是添加了一些 http 的 Message Converter。
4、 JacksonInitializer 主要用於生成 xml 轉換器的。
接著回到我們將的主體方法, prepareEnvironment 在調用完成 listeners.environmentPrepared(environment) 方法後,調用 bindToSpringApplication(environment) 方法,將 environment 綁定到 SpirngApplication 中。
接著將 enviroment 轉化為 StandardEnvironment 對象。
最後將 configurationProperties 加入到 enviroment 中, configurationProperties 其實是將 environment 中其他的 PropertySource 重新包裝了一遍,並放到 environment 中,這里主要的作用是方便 進行解析。
它主要是檢查是否存在 spring.beaninfo.ignore 配置,這個配置的主要作用是設置 javaBean 的內省模式,所謂內省就是應用程序在 Runtime 的時候能檢查對象類型的能力,通常也可以稱作運行時類型檢查,區別於反射主要用於修改類屬性,內省主要用戶獲取類屬性。那麼我們什麼時候會使用到內省呢,java主要是通過內省工具 Introspector 來完成內省的工作,內省的結果通過一個 Beaninfo 對象返回,主要包括類的一些相關信息,而在 Spring中,主要是 BeanUtils#Properties 會使用到,Spring 對內省機制還進行了改進,有三種內省模式,如下圖中紅色框框的內容,默認情況下是使用 USE_ALL_BEANINFO。假如設置為true,就是改成第三中 IGNORE_ALL_BEANINFO
首先是檢查 Application的類型,然後獲取對應的 ApplicationContext 類,我們這里是獲取到了 org.springframework.boot.web.servlet.context. 接著調用 BeanUtils.instantiateClass(contextClass); 方法進行對象的初始化。
最終其實是調用了 的默認構造方法。我們看一下這個方法做了什麼事情。這里只是簡單的設置了一個 reader 和一個 scanner,作用於 bean 的掃描工作。
我們再來看一下這個類的繼承關系
這里獲取 ExceptionReporter 的方式主要還是和之前 Listener 的方式一致,通過 getSpringFactoriesInstances 來獲取所有的 SpringBootExceptionReporter 。
其主要方法執行如下:
❹ Spring IOC源碼解析(02)PropertySource和PropertySources
PropertySource 主要是對屬性源的抽象,抽象除了熟悉源名稱和屬性源內容對象。其主要搭老者方法仍然是對這兩個欄位進行操作。
屬性源抽象類有很多子類的知薯實現,我們僅僅分析其中最常用含睜的一個,即: PropertiesPropertySource 。
其繼承關系如下:
逐層分析如下:
PropertySource 主要是對屬性源的抽象,抽象除了熟悉源名稱和屬性源內容對象。其主要方法仍然是對這兩個欄位進行操作。
屬性源抽象類有很多子類的實現,我們僅僅分析其中最常用的一個,即: PropertiesPropertySource 。
其繼承關系如下:
逐層分析如下:
PropertySources 是對屬性源列表操作的封裝。主要加入了 迭代器 、 可變性 和 集合操作 。這個類有一個唯一的子類,即: MutablePropertySources 。
MutablePropertySources 在 PropertySources 的基礎上,增加了 可變性 和 集合操作 。
❺ Spring源碼解析之BeanPostProcessor
源碼版本4.3.10-RELEASE
我們都知道,實缺咐現BeanPostProcessor介面,在bean初始化前後,spring會分別回調和。目的是保留擴展介面修改bean的屬性,甚至可以替換bean :
那spring究竟是如何做到的呢?我們一起來看看吧。
從spring容器啟動開始看起:new ("classpath:application.xml");
看AbstractApplicationContext.refresh();方法。
1、在 obtainFreshBeanFactory 里會解析xml配置,把所有bean注冊到beanDefinitionNames,包含我們自定義的BeanPostProcessor。
2、先看 prepareBeanFactory:
這里添加了三個spring默認的BeanPostProcessor。
3、再看 postProcessBeanFactory:
這個是(AbstractApplicationContext的子類)的方法咐猜,它同樣也添加了一個默認的BeanPostProcessor。
4、接著看 :
這里設置一下TempClassLoader,並重新添加LoadTimeWeaverAwareProcessor,後面會單獨開個文章分析內建的這衡扮型些類的作用。
5、繼續看 registerBeanPostProcessors:
registerBeanPostProcessors 會繼續添加一個內建Processor:
然後從容器取出所有Processor(包括我們自定義的)進行排序,再添加回beanFactory(先移除後添加),最後重新把ApplicationListenerDetector添加到末尾:
6、最後看一下調用過程吧, :
在這里beanFactory.preInstantiateSingletons();(在子類DefaultListableBeanFactory里實現)
再到其父類的AbstractBeanFactory.doGetBean->. createBean ->resolveBeforeInstantiation
就是在這里回調介面的2個方法,其實這里允許直接返回bean了。繼續看resolveBeforeInstantiation後面的 doCreateBean ,同樣會在里回調,不過只是針對某一類型的Processor:
doCreateBean 的另一個方法initializeBean里也會做回調:
執行完 createBean 後,再回到其父類的AbstractBeanFactory. getObjectForBeanInstance (該方法就是處理FactoryBean類型的bean)
調到其父類FactoryBeanRegistrySupport.getObjectFromFactoryBean
到.(在doGetObjectFromFactoryBean後執行,該方法正是初始化bean的過程)->
至此,完.
❻ Spring事件監聽機制源碼解析
1.Spring事件監聽體系包括三個組件:事件、事件監聽器搜哪,事件廣播器。
事件:定義事件類型和事件源,需要繼承ApplicationEvent。
事件監聽器:用來監聽某一類的事件,並且執行具體業務邏輯,需要實現ApplicationListener 介面或者需要用@ListenerEvent(T)註解。好比觀察者模式中的觀察者。
事件多播器:負責廣播通知所有監聽器,所有的事件監聽判猛器都注冊在了事件多播器中。好比觀察者模式中的被觀察者。Spring容器默認生成的是同步事件多播器。可以自定義事件多播器,定義為非同步方式。
創建 的過程中,會執行refresh()中的()方法。該方法先獲取bean工廠,然後判斷工廠是否包含了beanName 為 applicationEventMulticaster的bean。如果包含了,則獲取該bean,賦值給applicationEventMulticaster 屬性。如果沒有,則創建一個 對象,並且賦值給 applicationEventMulticaster 。實現了源碼如下:
監聽器的注冊有兩種,通過實現 ApplicationListener介面或者添加@EventListener註解。
注冊的邏輯實現在refresh()中的registerListeners()方法裡面。第一步,先獲取當前ApplicationContext中已經添加的 applicationListeners(SpringMVC源碼中有用到),遍歷添加到多播器中。第二步,獲取實現了ApplicationListener介面的listenerBeanNames集合,添加至多掘漏橋播器中。第三步,判斷是否有早期事件,如果有則發起廣播。
思考一下,上面的代碼中第二步為啥添加的是listenerBeanName?
如果監聽器是懶載入的話(即有@Lazy 註解)。那麼在這個時候創建監聽器顯然是不對的,這個時候不能創建監聽器。所以添加監聽器到多播器的具體邏輯放在初始化具體的監聽器之後。通過 BeanPostProcessor 的介面實現。具體的實現類是 ApplicationListenerDetector 。這個類是在 refreah()中prepareBeanFactory()方法中添加的。代碼如下:
在創建 的構造方法中,會執行org.springframework.context.annotation.AnnotationConfigUtils#(org.springframework.beans.factory.support.BeanDefinitionRegistry, java.lang.Object) 方法。這個方法中會添加兩個 beanDefs, 代碼如下:
EventListenerMethodProcessor:事件監聽器的BeanFactory後置處理器,在前期會創建 DefaultEventListenerFactory ,後期在創建好Bean之後,根據 EventListener 屬性,調用DefaultEventListenerFactory創建具體的 。
DefaultEventListenerFactory:監聽器的創建工廠,用來創建 。
EventListenerMethodProcessor 的類繼承圖如下:
在refreash的()中會調用 org.springframework.context.event.EventListenerMethodProcessor#postProcessBeanFactory方法,獲取EventListenerFactory 類型的 Bean。代碼如下:
在 org.springframework.beans.factory.support.DefaultListableBeanFactory#preInstantiateSingletons 方法中,創建完所有的單例Bean 之後,會遍歷所有Bean是否實現了 SmartInitializingSingleton 介面。如果實現介面會執行該 Bean 的 afterSingletonsInstantiated() 方法。代碼如下:
org.springframework.context.event.EventListenerMethodProcessor#afterSingletonsInstantiated 中會調用私有方法 processBean()進行 ApplicationEventAdatper 的創建。代碼如下:
可以通過調用 org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType) 方法進行事件的調用。代碼如下:
中的 multicasEvent,invokeListener,doInvokeListener 三個方法代碼如下:
SpringMVC中就是通過Spring的事件機制進行九大組件的初始化。
監聽器定義在FrameworkServlet類中,作為內部類。代碼如下:
監聽器的添加在org.springframework.web.servlet.FrameworkServlet# 中進行。通過SourceFilteringListener進行包裝。添加代碼如下:
在refresh中的registerListeners方法進行添加,代碼如下:
在refresh中的finishRefresh()方法中,會調用publishEvnet(new ContextRefreshedEvent(this))發布事件。進行多播器廣播,代碼如下
最終會調到FrameworkServlet.this.onApplicationEvent(event)。
❼ Spring GateWay 路由源碼分析
Spring Cloud提供了兩套方便我們編寫網關的中間件,分別是zuul和Spring GateWay,在zuul1的IO模型是使用BIO(圖1-1)。而zuul2對IO模型使用NIO進行了重構(圖1-2)。而Spring GateWay的IO模型是使用NIO。而在Netflix發布zuul2的時候Spring Cloud已經開始不集成到Spring Cloud中,因為Spring Cloud 等著zuul2集成太久,才有了Spring Gateway。Spring GateWay的架構是基於Spring webflux的基礎上開發的。而對webflux的RP中涉及的Back Pressure、Stream、asynchronous好處不多說哈哈。
在Spring mvc是通過HandlerMapping解析請求鏈接,然後根據請求鏈接找到執行這個請求Controller類 。而在Spring GateWay中也是使用HandlerMapping對請求的鏈接進行解析匹配對應的Route進行代理轉發到對應的服務。圖2-1為整個請求的流程,用戶請求先通過DispatcherHandler找到對應GateWwayHandlerMapping,再通過GateWwayHandlerMapping解析匹配到對應的Handler。Handler處理完後,再經過Filter,最終到Proxied Service.
1.請求先由DispatcherHanlder進行處理,DispatcherHanlder初始化的時候會從IOC中查找實現HandlerMapping介面的實現類。然後保存到內部變數handlerMappings中,旦戚飢DispatcerHandler調用Handler方法迭代handler
Mappings中的HandlerMapping,
2.這里只講解RoutePredicateHandlerMapping,因此然後調用RoutePredicateHandlerMapping中的獲取路由的方法,當RoutePredicateHandlerMapping獲取到對應的路由的時候會將Route存儲到ServerWebExchanges的屬性中,然後返回實現了WebHandler介面的FilteringWebHandler。FilteringWebHandler是一個存放過濾器的Handler。模返
3.最後DispatcherHanlder通過SimpleHandlerAdapter適配器的方式調用FilteringWebHandler的handler方法,FilteringWebHandler調用所有的過濾器,包括proxy filter。通過proxyFilter請求被代理的服務。處理完畢後,並仔桐將Response響應回去。
圖3-1為handler類關系圖。這里主要涉及到Spring GateWay相關類的探討。如:Spring Webflux使用到的RouteFuntionMapping和SimpleUrlHandlerMapping等不做探討。
HandlerMapping和Ordered介面主要定義了獲取getHandler和當前hanler載入順序。AbstractHandlerMapping在getHanlder封裝了CORS處理。因為所有Handler都可能會涉及到CORS的處理,抽象到AbstractHandlerMapping處理,再提供了getHandlerInternal讓子類實現具體的查找Handler的方法。
RoutePredicateHandlerMapping是處理獲取路由的hanlder。Route
PredicateHandlerMapping中的RouteLocator是存儲了我們啟動的時候載入的路由對象信息。獲取路由的時候,調用RoutePredicateHanlderMapping的getHandlerInternal方法從RouteLocator獲取路由存放在ServerWebExchange中,返回webFilter。
RouteLocator主要作用是提供獲取路由的類型。我們在分析Route
PredicateHandlerMapping的時候,知道RoutePredicateHandlerMapping獲取路由是通過RouteLocator進行獲取的。那麼我們這里分析下RouteLocator載入路由。
Route主要為三部分:
最總的 RouteLocator是CachingRoutelocator。載入過程是自上而下進行創建。
第二種方式是通過Properties文件進行創建路由。Properties路由的創建包括:和.
第三種方式是通過MYSQL或者Reids、內存()方式創建路由。實現RouteDefinitionRepository介面實現介面中的方式。為默認方式。
Filter我們區分為全局Filter和RouteFilter
在轉發過程分析中我們知道最終的代理請求是通過一個Proxy Filter進行請求Proxy Service,那麼這個Proxy Filter就是NettyRoutingFilter。通過下面的源碼我們可以看到在 proxyRequest.sendHeaders() .send(request.getBody().map(dataBuffer -> ((NettyDataBuffer) dataBuffer).getNativeBuffer())); 中請求Proxy Service.
❽ 詳解Spring mvc工作原理及源碼分析
Model 模型層 (javaBean組件 = 領域模型(javaBean) + 業務層 + 持久層)
View 視圖層( html、jsp…)
Controller 控制層(委託模型層進行數據處理)
springmvc是一個web層mvc框架,類似struts2。
springmvc是spring的部分,其實就是spring在原有基礎上,又提供了web應用的mvc模塊。
實現機制:
struts2是基於過濾器實現的。
springmvc是基於servlet實現的。
運行速度:
因為過濾器底層是servlet,所以springmvc的運行速度會稍微比structs2快。
struts2是多例的
springmvc單例的
參數封裝:
struts2參數封裝是基於屬性進行封裝。
springmvc是基於方法封裝。顆粒度更細。
⑴ 用戶發送請求至DispatcherServlet。
⑵ DispatcherServlet收到請求調用HandlerMapping查詢具體的Handler。
⑶ HandlerMapping找到具體的處理器(具體配置的是哪個處理器的實現類),生成處理器對象及處理器攔截器(HandlerExcutorChain包含了Handler以及攔截器集合)返回給DispatcherServlet。
⑷ DispatcherServlet接收到HandlerMapping返回的HandlerExcutorChain後,調用HandlerAdapter請求執行具體的Handler(Controller)。
⑸ HandlerAdapter經過適配調用具體的Handler(Controller即後端控制器)。
⑹ Controller執行完成返回ModelAndView(其中包含邏輯視圖和數據)給HandlerAdaptor。
⑺ HandlerAdaptor再將ModelAndView返回給DispatcherServlet。
⑻ DispatcherServlet請求視圖解析器ViewReslover解析ModelAndView。
⑼ ViewReslover解析後返回具體View(物理視圖)到DispatcherServlet。
⑽ DispatcherServlet請求渲染視圖(即將模型數據填充至視圖中) 根據View進行渲染視圖。
⑾ 將渲染後的視圖返回給DispatcherServlet。
⑿ DispatcherServlet將響應結果返回給用戶。
(1)前端控制器DispatcherServlet(配置即可)
功能:中央處理器,接收請求,自己不做任何處理,而是將請求發送給其他組件進行處理。DispatcherServlet 是整個流程的控制中心。
(2)處理器映射器HandlerMapping(配置即可)
功能:根據DispatcherServlet發送的url請求路徑查找Handler
常見的處理器映射器:BeanNameUrlHandlerMapping,SimpleUrlHandlerMapping,
,(不建議使用)
(3)處理器適配器HandlerAdapter(配置即可)
功能:按照特定規則(HandlerAdapter要求的規則)去執行Handler。
通過HandlerAdapter對處理器進行執行,這是適配器模式的應用,通過擴展多個適配器對更多類型的處理器進行執行。
常見的處理器適配器:HttpRequestHandlerAdapter,,
(4)處理器Handler即Controller(程序猿編寫)
功能:編寫Handler時按照HandlerAdapter的要求去做,這樣適配器才可以去正確執行Handler。
(5)視圖解析器ViewReslover(配置即可)
功能:進行視圖解析,根據邏輯視圖名解析成真正的視圖。
ViewResolver負責將處理結果生成View視圖,ViewResolver首先根據邏輯視圖名解析成物理視圖名即具體的頁面地址,再生成View視圖對象,最後對View進行渲染將處理結果通過頁面展示給用戶。
springmvc框架提供了多種View視圖類型,如:jstlView、freemarkerView、pdfView...
(6)視圖View(程序猿編寫)
View是一個介面,實現類支持不同的View類型(jsp、freemarker、pdf...)
引入相關依賴:spring的基本包、springmvc需要的spring-webmvc,日誌相關的slf4j-log4j12,jsp相關的jstl、servlet-api、jsp-api。
因為DispatcherServlet本身就是一個Servlet,所以需要在web.xml配置。
一、使用默認載入springmvc配置文件的方式,必須按照以下規范:
①命名規則:-servlet.xml ====> springmvc-servlet.xml
②路徑規則:-servlet.xml必須放在WEB-INF下邊
二、如果要不按照默認載入位置,則需要在web.xml中通過標簽來指定springmvc配置文件的載入路徑,如上圖所示。
將自定義的 Controller 處理器配置到 spring 容器中交由 spring 容器來管理,因為這里的 springmvc.xml 配置文件中處理器映射器配置的是 BeanNameUrlHandlerMapping ,根據名字可知這個處理器映射器是根據 bean (自定義Controller) 的 name 屬性值url去尋找執行類 Handler(Controller) , 所以bean的name屬性值即是要和用戶發送的請求路徑匹配的 url 。
根據視圖解析路徑:WEB-INF/jsps/index.jsp
功能:根據bean(自定義Controller)的name屬性的url去尋找執行類Controller。
功能:自定義的處理器(Controller)實現了Controller介面時,適配器就會執行Controller的具體方法。
會自動判斷自定義的處理器(Controller)是否實現了Controller介面,如果是,它將會自動調用處理器的handleRequest方法。
Controller介面中有一個方法叫handleRequest,也就是處理器方法。
因此,自定義的Controller要想被調用就必須實現Controller介面,重寫Controller介面中的處理器方法。
❾ 【Spring源碼配置文件解析】2. xml注入配置信息 & @Value
這樣xml中配置的bean的屬性就會被注入配置文件裡面對應的值
首先xml中的bean會在掃描的過程中封裝成BeanDefinition對象,property標簽會被弄成一個ProprotyValue的集合放在BeanDefinition的ProprotyValues變數中,所以在xml解析完成之後的BeanDefinition的ProprotyValues變數是這樣的
上節這個類收集了environment配置信息和本地配置信息,並把它放在了的propertySources屬性中
最後創建了一個StringValueResolver對象會調用來處理配置信息的替換
接下來就是取出所有的BeanDefinition,看看beanDefinition中的屬性中是否有${}表達式,有的話就替換
很多的屬性都可以用${}來引用配置信息局橡物
重點看看屬性是如何被替換的
在BeanDefinitionVisitor.resolveValue方法中,String類型的走這
最終會調到創建的StringValueResolver匿名對象的實現方法中
這個匿名對象實現的方法如游又會調用來替換值,前面有提到所有的配置信息都在.propertySources中,那麼接下來的工作就是從這個容器中找到對應的配置信息的key所對應的value
這里在入參時會創建一個PlaceholderResolver的匿名桐液對象,實現的resolvePlaceholder方法將會調用.getPropertyAsRawString()
最後返回了被替換成對應配置信息的值
這里就會調用前面創建的匿名對象的實現方法,方法體重會調用調用.getPropertyAsRawString(),去用key獲取對應的配置信息
.getPropertyAsRawString()
PropertiesPropertySource對象內部有name,和source,source是一個泛型,當前類型為Properties,PropertiesPropertySource需要實現getProperty方法,其實就是從source中獲取屬性值
最後調到了Properties類的get方法,返回value
對每個beanDefination都這樣操作過一遍
注意這個StringValueResolver的resolveStringValue會調用的方法來處理配置信息的替換,持有了所有的配置信息。 那麼後面@Value的解析也將StringValueResolver來完成
@Value的解析工作是在Bean實例化後,屬性注入的時候從配置文件找出並設置進去的
populateBean方法
只有string類型的才能@Value註解,才需要處理
又是這個容器,之前的doProcessProperties放進去的StringValueResolver
最後又會回到這個地方解析並注入值,和xml方式獲取配置信息是一樣的
找到對應的配置信息之後,反射設置這個屬性的值
❿ SpringSecurity源碼整體解析
spring對請求的處理過程如下:
而security所有認證邏輯作為特殊的一個Filter加入到spring處理servelet的過濾鏈中,即FilterChainProxy;
而這個FilterChainProxy內部又有多個過濾器鏈FilterChain,每個鏈有matcher,用於匹配請求,請求來時選擇最先匹配的一條過濾器鏈做許可權認證,每條過濾器鏈又由多個多濾器Filter依序連接而成。如下圖:
configuator配置類裝配到builder類中,builder類藉助confuguator類構造filter或者filterChain
SpringSecurity有兩個重要的builder:
首先生成FilterChainProxy實例,將FilterChainProxy實例再封裝到DelegatingFilterProxy(java web的標准過濾器),作為一個web的Filter再注冊到spring上下文。
至於this.webSecurity.build()內部怎麼實現的,後面再講。
將FilterChainProxy再封裝到DelegatingFilterProxy,在注入到spring上下文。有兩種方式,如下:
上面講到了通過調用this.webSecurity.build()方法產生FilterChainProxy實例,現在仔細分析具體怎麼實現的。
由上可見,主要是init、configure、performBuild三個方法
2.1. init(WebSecurity)
會遍歷所有之前載入好的配置類configuator(adaptor),調用其init。
其中配置類的init方法,主要是構造了HttpSecurity,放入到securityFilterChainBuilders;並在postBuild之後設置inteceptor到websecurity。可見WebSecurityConfigurerAdapter類的init方法實現:
2.2. configure(WebSecurity)
會遍歷所有之前載入好的配置類configuator(adaptor),調用其configure
配置類的configure方法,主要是配置上一步構造好的HttpSecurity實例,將其相關的configuator配置類裝配到HttpSecurity實例。可見WebSecurityConfigurerAdapter類的configure方法實現:
我們在定義自己的過濾器鏈的時候,可以繼承WebSecurityConfigurerAdapter重寫configure方法,比如:
2.3. performBuild(WebSecurity)
遍歷securityFilterChainBuilders(其實就是HttpSecurity)列表調用其build方法,生成SecurityFilterChain實例,最後利用多個SecurityFilterChain實例組成List,再封裝到FilterChainProxy。
securityFilterChainBuilders(其實就是HttpSecurity)的build方法,內部最後也是調用自己的init、configure、performBuild。
2.3.1. init(HttpSecurity)
會遍歷所前含有之前載入好的配置類configuator,調用其init
配置類的init一般是配置HttpSecurity。以HttpBasicConfigurer配置類為例:
2.3.2. configure(HttpSecurity)
會遍歷所有之前載入好的配置賀襪類configuator,調用其configure
配置類的configure一般構造Filter,添加到HttpSecurity的禪悔激Filter列表中,作為過濾器鏈的其中一個。以HttpBasicConfigurer配置類為例:
2.3.3. performBuild(HttpSecurity)
將List<Filter>以及matcher封裝成SecurityFilterChain
請求到達的時候,FilterChainProxy的dofilter()方法內部,會遍歷所有的SecurityFilterChain,匹配url,第一個匹配到之後則調用該SecurityFilterChain中的List<filter>依次做認證或鑒權,執行最後調用外層傳入的filterchain,將控制權交給上層過濾鏈,即spring 的過濾鏈。
核心邏輯如下: