Spring框架的七大模塊
- Spring Core:框架的最基礎部分,提供IoC容器,對bean進行管理
- Spring Context:繼承BeanFactory,提供上下文信息,拓展出JNDI, EJB, 電子郵件,國際化等功能
- Spring DAO:提供了JDBC的抽象層,還提供了聲明性事務管理方法
- Spring ORM:提供了JPA, JDO, Hibernate, MyBatis等ORM映射層
- Spring AOP:集成了所有AOP功能
- Spring Web:提供了基礎的Web開發的上下文信息,現有的web框架,如JSF, Tapestry, Structs等,提供了集成
- Spring Web MVC:提供了Web應用de Model-View-Controller全功能實現
Bean定義5種作用域
- singleton(單例)
- prototype(原型)
- request
- session
- global session
Spring IoC初始化流程
☞ 第一步 Resource定位
Resource是Spring中用於封裝I/O操作的接口。正如前面所見,在創建spring容器時,通常要訪問XML配置文件,除此之外還可以通過訪問文件類型、二進制流等方式訪問資源,還有當需要網絡上的資源時可以通過訪問URL,Spring把這些文件統稱爲Resource,常用的Resource資源類型如下:
- FileSystemResource:以文件的絕對路徑方式進行訪問資源,效果類似於Java中的File
- ClassPathResource:以類路徑的方式訪問資源,效果類似於this.getClass().getResource("/).getPath("")
- ServletContextResource:web應用根目錄的方式訪問資源,效果類似於request.getServletContext().getRealPath("")
- UrlResource:訪問網絡資源的實現類,如file: http: ftp: 等前綴的資源對象
- ByteArrayResource:訪問字節數組資源的實現類
Spring提供了ResourceLoader接口用於實現不同的Resource加載策略,該接口的實例對象中可以獲取一個resource對象,也就是說將不同的Resource實例的創建交給ResourceLoader的實現類來處理。
☞ 第二步 通過返回的Resource對象,進行BeanDefinition的載入
1. 什麼是BeanDefinition? BeanDefinition與Resource的聯繫?
官方文檔中對BeanDefinition的解釋如下:
A BeanDefinition describes a bean instance, which has property values, constructor argument values, and further information supplied by concrete implementations.
Load bean definitions from the specified resource.
總之,BeanDefinition相當於一個數據結構,這個數據結構的生成過程是根據定位的Resource資源對象中的bean而來的,這些bean在Spring IoC容器內部表示成了BeanDefinition這樣的數據結構,IoC容器對Bean的管理和依賴注入的實現都是通過操作BeanDefinition來進行的。
2. 如何將BeanDefinition載入到容器?
在Spring中配置文件主要格式是XML,對於用來讀取XML型資源文件來進行初始化的IoC容器而言,該類容器會使用到AbstractXmlApplicationContext類,該類定義了一個名爲loadBeanDefinitions(DefalutListableBeanFactory beanFactory)的方法用來獲取BeanDefinition。
☞ 第三步 將BeanDefinition註冊到容器中
最終Bean配置會被解析成BeanDefinition並與beanNames, Alias以同封裝到BeanDefinitionHolder類中,之後beanFactory.registerBeanDefinition(beanName, bdHolder.gerBeanDefinition())註冊到DefaultListableBeanFactory.beanDefinitionMap中。之後客戶端如果要獲取Bean對象,Spring容器會根據註冊的BeanDefinition進行實例化。
BeanDefinition加載流程
定義BeanDefinitionReader解析xml的document,BeanDefinitionDocumentReader解析document成beanDefinition
DI依賴注入流程(實例化,處理Bean之間的依賴關係)
過程在IoC初始化後,依賴注入的過程是用戶第一次向IoC容器索要Bean時觸發。
- 如果設置lazy-init=true,會在第一次getBean的時候才初始化Bean,lazy-int=false時,會在容器啓動的時候直接初始化(singleton bean)
- 調用BeanFactory.getBean()生成bean
- 生成bean過程運用裝飾器模式產生的bean都是beanWrapper (bean的增強)
☞ 依賴注入如何處理Bean之間的依賴關係
在beanDefinition載入時,如果bean有依賴關係,通過佔位符來代替,在調用getBean時候,如果遇到佔位符,從IoC裏獲取bean注入到本實例來。
Bean的生命週期
- 實例化Bean:IoC容器通過獲取BeanDefinition對象中的信息進行實例化,實例化對象被包裝在BeanWrapper對象中
- 設置對象屬性(DI):通過BeanWrapper提供的設置屬性的接口完成屬性依賴注入
- 注入Aware接口(BeanFactoryAware,可以用這個方法來獲取其它Bean,ApplicationContextAware):Spring會檢測該對象是否實現了xxxAware接口,並將相關xxxAware實例注入給bean
- BeanPostProcessor:自定義的處理(分前置處理和後置處理)
- InitializingBean和init-method:執行我們自定義的初始化方法
- 使用
- destory:bean的銷燬
Spring的IoC注入方式
- 構造器注入
- setter方法注入
- 註解注入
- 接口注入
Soring的循環依賴
☞ 什麼是循環依賴?
循環依賴其實就是循環引用,也就是兩個或者兩個以上的bean相互持有對方,最終形成閉環。比如A依賴於B,B依賴於C,C依賴於A,如圖:
Spring中循環依賴場景有:
- 構造器的循環依賴
- field屬性的循環依賴
☞ 怎麼檢測是否存在循環依賴?
Bean在創建的時候可以給Bean打標,如果遞歸調用回來發現正在創建中的話,即說明存在循環依賴。
☞ 如何解決循環依賴?
Spring的單例對象的初始化主要分爲三步:(1)CreateBeanInstance實例化(調用對象的構造方法實例化對象);(2)populateBean填充屬性(多bean的依賴屬性進行填充);(3)InitializeBean初始化(調用spring xml中的init方法)
從上面單例bean初始化步驟可以看到,循環依賴主要發生在第一、第二部,也就是構造器循環依賴和filed循環依賴。
在Spring容器整個生命週期內,有且只有一個對象,對象應該存在Cache中,Spring爲了解決單例的循環依賴問題,使用了三級緩存。
這三個緩存分別指:
- singletonObjects:第一級緩存,裏面放置的是實例化好的單例對象;
- earlySingletonObjects:第二級緩存,裏面存放的是提前曝光的單例對象
- singletonFactories:第三級緩存,裏面存放的是要被實例化的對象的對象工廠
創建bean的時候Spring首先從一級緩存singletonObjects中獲取,如果獲取不到,並且對象正在創建中,就再從二級緩存earlySingletonObjects中獲取,如果還是獲取不到就從三級緩存singletonFactories中取(Bean調用構造函數進行實例化後,即使屬性還爲填充,就可以通過三級緩存向外提前暴露依賴的引用值,根據對象引用能定位到堆中的對象,其原理是基於Java的引用傳遞),取到後從三級緩存移動到二級緩存,完全初始化之後將自己放入到一級緩存中供其他使用。
因爲加入singletonFactories三級緩存的前提是執行了構造器,所以構造器的循環依賴沒法解決。
Spring中使用了哪些設計模式?
- 工廠模式:spring中的BeanFactory就是簡單工廠模式的體現,根據傳入唯一的標識來獲得bean對象;
- 單例模式:提供了全局的訪問點BeanFactory
- 代理模式:AOP功能的原理就是使用了代理模式
- 裝飾器模式:依賴注入就需要使用BeanWrapper
- 觀察者模式:Spring中Observer模式常用的地方是listener的實現,如ApplicationListener
- 策略模式:Bean實例化的時候決定採用何種方式初始化bean實例(反射或者GCLIB動態字節碼生成)
AOP核心概念
- 切面(aspect):類是對物體特徵的抽象,切面就是對橫切關注點的抽象
- 橫切關注點:對那些方法進行攔截,攔截後怎麼處理,這些關注點稱之爲橫切關注點
- 連接點(joinpoint):被攔截到的點,因爲spring只支持方法類型的連接點,所以在Spring中連接點指的就是攔截到的方法,實際上連接點還可以是字段或者構造器
- 切入點(pointcut):對連接點進行攔截的定義
- 通知(advice):所謂通知指的就是攔截到連接點之後要執行的代碼,通知分爲前置、後置、異常、最終、環繞通知五類
- 目標對象:代理的目標對象
- 織入(weave):將切面應用到目標對象並導致代理對象創建的過程
- 引入(introduction):在不修改代碼的前提下,引入可以在運行期爲類動態地添加方法或字段
解釋一下AOP
傳統OOP開發代碼邏輯是自上而下地,這個過程中會產生一些橫切性問題,這些問題與我們主業務邏輯關係不大,會散落在代碼地各個地方,造成難以維護,AOP的思想就是把業務邏輯與橫切的問題進行分離,達到解耦的目的,提高代碼重用性和開發效率
AOP主要應用場景
- 記錄日誌
- 監控性能
- 權限控制
- 事務管理
AOP源碼分析
- @EnableAspectAutoProxy給容器(beanFactory)中註冊一個AnnotationAwareAspectAutoJProxyCreator對象
- AnnotationAwareAspectJAutoProxyCreator對目標對象進行代理對象的創建,對象內部,是封裝JDK和CGlib兩個技術,實現動態代理對象的創建(創建代理對象的過程中,會先創建一個代理工廠,獲取到所有的增強器(通知方法),將這些增強器和目標類注入到代理工廠,再用代理工廠創建對象)
- 代理對象執行目標方法,得到目標方法的攔截器鏈,利用攔截器的鏈式機制,依次進入每一個攔截器進行執行
AOP使用哪種代理對象
- 當bean的實現中存在接口或者是Proxy的子類,使用JDK動態代理;不存在接口,Spring採用CGLIB來生成代理對象
- JDK動態代理主要涉及到java.lang.reflect包中的兩個類:Proxy和InvocationHandler
- Proxy利用InovationHandler(定義橫切邏輯)接口動態創建目標類的代理對象
JDK動態代理
- 通過bind方法建立代理與真實對象關係,通過Proxy.newProxyInstance(target)生成代理對象
- 代理對象通過反射invoke方法實現調用真實對象的方法
靜態代理與動態代理區別
- 靜態代理:程序運行前代理類的.class文件就存在了
- 動態代理:在程序運行時利用反射動態創建代理對象(複用性、易用性、更加集中)
CGLIB和JDK動態代理區別
- JDK動態代理必須提供接口才能使用
- CGLIB不需要,只要一個非抽象類就能實現動態代理