文末福利:掃描文末二維碼,回覆關鍵字"SpringIoC"獲取總結的完整思維導圖。
IoC基本概念
IoC(Inverse of Control 控制反轉):接口實現類的選擇控制權,從調用類中移除,轉交給第三方決定,即由Spring容器藉由Bean配置來進行控制。
IoC的概念不太直觀,後來有人提出DI的概念用來代替IoC。
DI(Dependency Injection 依賴注入):調用類對某一接口的實現類的依賴關係由第三方(容器或協作類)注入,以移除調用類對某一接口實現類的依賴。
IoC的類型有:
- 構造函數注入
- 屬性注入
- 接口注入(效果與屬性注入類似,不推薦使用)
Spring支持構造函數注入和屬性注入。
IoC的實現方式–反射
Java語言允許通過程序化的方式間接對Class進行操作。Class文件由類裝載器裝載後,在JVM中將形成描述Class結構的元信息對象,通過該元信息對象可以獲知Class的結構信息。爲使用程序化的方式操作Class對象開闢了途徑。Java反射涉及到的主要類如下:
ClassLoader
類裝載器:負責尋找類的字節碼文件並構造出類在JVM內部表示對象的組件
類裝載步驟:
- 裝載:查找和導入Class文件
- 鏈接
① 校驗:檢查載入Class文件數據的正確性
② 準備:給類的靜態變量分配存儲空間
③ 解析(可選):將符號引用轉換成直接引用 - 初始化:對類的靜態變量、靜態代碼塊執行初始化工作
JVM運行時產生3個ClassLoader:根裝載器、ExtClassLoader、AppClassLoader
根裝載器不是ClassLoader的子類,它是用C++編寫,負責裝載JRE核心類庫:rt.jar、charsets.jar等
ExtClassLoader、AppClassLoader都是ClassLoader的子類。Ext負責裝載JRE擴展目錄ext中的JAR類包,AppClassLoader負責裝載Classpath路徑下的類包。
三個裝載器存在父子層級關係,默認情況下使用AppClassLoader裝載應用程序中的類。JVM裝載類時的全盤負責委託機制(/雙親委派機制):
- 該類所依賴及引用的類也由這個ClassLoader載入(除非顯式使用其他ClassLoader);(全盤負責)
- 先委託父裝載器尋找目標類,找不到的情況下從自己的類路徑中查找並裝載目標類。(委託機制)
主要方法:
- defineClass(String name, byte[] b, int off, int len):將類文件的字節數組轉換成JVM內部的java.lang.Class對象。字節數組可以從本地文件系統、遠程網絡獲取。參數name爲字節數組對應的全限定類名。該方法由JVM裝載類時調用構造Class對象
- getParent() 獲取裝載器的父裝載器。除根裝載器,所有類裝載器有且僅有一個父裝載器。ExtClassLoader的父裝載器是根裝載器,非Java語言編寫,所以調用該方法時返回null;
- loadClass(name)、loadClass(name, resolve):通過全限定類名裝載類;
- findSystemClass(name):從本地文件系統載入Class文件;
- findLoadedClass(name):查看ClassLoader是否已經裝載某個類,已裝入會返回Class對象,否則返回null。
Class文件、類裝載器、Class類對象之間的引用關係如圖:
主要的反射類
- Constructor:構造函數反射類,可通過Class對象的getContructor(s)方法獲得,其主要的方法是newInstance可創建類對象實例,相當於new關鍵字。
- Method:類方法的反射類,可通過Class對象的getDeclaredMethod(s)方法獲得,其主要的方法是invoke執行指定對象的這個方法。同時,可通過其他方法獲得該Method的返回、參數類型、異常、註解信息
- Field:類成員變量反射類,可通過Class對象的getDeclaredField(s)方法獲得,其主要方法是set爲指定對象域設置值,基本類型域通過setInt/setBoolean等設置值。
資源訪問與加載
Spring通過訪問配置文件,或者讀取配置類進行容器的初始化、啓動、刷新等操作,資源訪問作爲Spring框架的底層基礎實施,具體是怎樣實現的呢?提供了哪些擴展的方式?下面具體來看……
Spring設計了Resource接口,爲應用提供了更強的底層資源訪問能力。通過Spring接口,可以將Spring的配置信息放置到任何地方,比如數據庫、LDAP等。
Resource在Spring框架中起着不可或缺的作用,Spring使用Resource裝載各種資源,包括配置文件資源、國際化屬性文件資源等。具體實現類如下圖:
資源實現類
-
WritableResource:字接口類定義了可寫資源相關方法,其實現類有FileSystemResource、PathResource。
-
ClassPathResource:類路徑下的資源,資源以類路徑的方式表示。
-
UrlResource:封裝了java.net.URL,能夠訪問通過URL表示的資源,如文件系統資源、HTTP資源等。
-
ServletContextResource:爲訪問Web容器上下文中資源定義的類。以Web應用根路徑的方式加載資源。
-
ByteArrayResource:二進制數組表示的資源。
-
InputStreamResource:以輸入流返回表示。
-
FileSystemResource:文件系統資源。
-
PathResource:表示任何通過URL、Path、系統文件路徑表示的資源。
資源加載
有了針對資源的表達方式,加載資源上需要由資源的路徑地址以及資源加載器實現,可以將資源文件看作代加工的物料,加載器看作加工處理器,Spring的體系中隨處可見這樣的設計思路。
資源表達式
不需要顯示指定具體Resource實現類,通過地址自動識別,支持的以下地址及對應的實現類:
file:/xxx、http://xxx、ftp://xxx 使用UrlResource裝載資源;classpath:/xxx使用ClassPathResource裝載;沒有前綴則使用ApplicationContext具體實現類對應類型的Resource。
“classpath:” 還有一種"classpath*:"的前綴,表示在多個包名中進行匹配,否則僅匹配第一個出現的包名,對於分模塊打包需要加載多個配置文件的情況下適用。
Ant風格支持的匹配符:
- ?:匹配文件名中任意一個字符。
- *:匹配文件名中任意多個字符。
- **:匹配多層目錄
資源加載器
ResourceLoader接口僅支持帶資源類型前綴的表達式,ResourcePatternResolver擴展ResourceLoader接口,支持Ant風格資源路徑表達式。PathMatchingResourcePatternResolver是Spring提供的標準實現。
Spring容器-框架級接口
我們平常所說的Spring容器具體在Spring框架中,指代的是BeanFactory、ApplicationContext以及在Web環境中使用的WebApplicationContext三個容器級別的接口,在啓動Spring程序時,也是通過其中某一個接口進行容器的啓動工作。那這三個接口具體是什麼含義呢,他們之間又有什麼相同點和區別呢,他們具體又有哪些實現類供我們實際使用呢?
BeanFactory
BeanFactory可看作是Spring內部的基礎設施,是對容器的抽象,定義了Bean工廠的基本方法,供Spring內部使用;ApplicationContext在BeanFactory的基礎上,提供了更豐富的功能支持,比如國際化支持和框架事件體系等。
對於Spring的使用者,我們一般使用ApplicationContext,而非底層的BeanFactory。爲了更好的理解Spring框架,我們還是有必要理解BeanFactory的設計思路的,BeanFactory中定義個主要方法爲getBean(beanName),通過bean名稱獲得bean,通過其他接口擴展BeanFactory的功能,具體如下:
- ListableBeanFactory 定義訪問容器中基本信息的方法:查看個數、獲取某一類型Bean的配置名、容器中是否包含某一Bean等。
- HierarchicalBeanFactory 父子級聯IoC容器接口,子容器可以通過該接口方法訪問父容器,而父容器無法訪問子容器。子容器可以擁有一個和父容器id相同的Bean。增強了Spring框架的擴展性和靈活性,第三方可以通過編程方式添加子容器,提供一些額外功能。比如:SpringMVC中,展現層Bean位於一個子容器,可訪問業務層和持久才能夠的bean,反過來卻無法訪問。
- ConfigurableBeanFactory 重要接口,增強可定製性。包括:設置類裝載器、屬性編輯器、容器初始化後置處理器等方法。
- AutowireCapableBeanFactory 定義將容器中的bean按某種規則(名字或類型匹配)進行自動裝配的方法
- SingletonBeanRegistry 允許運行期向容器註冊單實例Bean的方法
- BeanDefinitionRegistry 提供向容器手工註冊BeanDefinition方法。(每一個節點元素對應Spring容器中的BeanDefinition對象表示)
BeanFactory常用實現類爲DefaultListableBeanFactory,配合使用XmlBeanDefinitionReader讀取配置信息,啓動容器:
public class BeanFactoryTest {
public static void main(String[] args) {
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource resource = resolver.getResource("classpath:com/smart/beanfactory/beans.xml");
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(bf);
reader.loadBeanDefinitions(resource);
Car car = bf.getBean("car", Car.class);
car.introduce();
}
}
ApplicationContext
ApplicationContext擴展自BeanFactory的子接口,同時繼承了容器事件、國際化消息訪問相關的接口,功能更加豐富,具體繼承體系如下圖:
- ApplicationEventPublisher 讓容器擁有發佈應用上下文事件的功能,包括啓動、關閉事件等。實現了ApplicationListener事件監聽接口的Bean可以接收到容器事件,並對事件進行響應處理。AbstractApplicationContext中存在一個ApplicationEventMulticaster,負責保存所有監聽器,以便容器產生上下文事件時通知這些事件監聽器
- MessageSource 爲應用提供i18n國際化消息訪問的功能
- ResourcePatternResolver 其實現類都實現了類似於PathMatchingResourcePatternResolver的功能,可通過帶前綴的Ant風格資源文件路徑裝載Spring的配置文件。
- LifeCycle start、stop兩方法,用於控制異步處理過程。ApplicationContext會將start/stop信息傳遞給容器中所有實現了該接口的Bean,以達到控制JMX、任務調度等目的。
實現類
ApplicationContext主要的實現類有ClassPathXmlApplicationContext、FileSystemXmlApplicationContext、AnnotationConfigApplicationContext、GenericGroovyApplicationContext。
AnnotationConfigApplicationContext爲基於類註解的配置方式提供專門的實現類;GenericGroovyApplicationContext是爲Groovy DSL配置方式提供的專門的實現類
對比BeanFactory
- BeanFactory初始化時並未實例化Bean,直到第一次訪問某個Bean時才實例化目標Bean,存在“第一次懲罰”的問題,使用ApplicationContext啓動容器時就會加載所有singleton bean,因此啓動會稍慢一些,但帶來的好處是及早發現潛在的配置問題,提升運行時效率;
- ApplicationContext能夠自動識別出配置文件中定義的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor(統稱爲後處理器),並自動將他們註冊到應用上下文中,而BeanFactory需要手動調用方法註冊。
WebApplicationContext
專門爲Web應用準備,除了prototype、singleton,另外爲Bean新增了三個作用域:request、session、global session。類繼承體系爲:
Spring的Web應用上下文與Web容器可以實現互相訪問:
WebApplicationContext的初始化方式區別於BeanFactory和ApplicationContext,需要配置在Web容器啓動的基礎上進行啓動,具體是配置ContextLoaderServlet或ContextLoaderListener完成容器的初始化工作。
Bean生命週期
在瞭解了Spring容器核心接口的情況下,繼續討論被Spring容器管理的Bean,具體會經過哪些階段完成初始化,以及銷燬的過程又是怎樣?
Spring初始化Bean經過了若干步驟,具體流程如下圖所示:
① 若容器註冊了InstantiationAwareBeanPostProcessor接口,則調用其postProcessBeforeInstantiation方法。
② 根據配置調用類的構造函數或工廠方法實例化Bean。
③ 與步驟①對應,調用其xxxAfterInstantiation方法。
④ 與步驟①對應,設置屬性值前,調用其xxxPropertyValues方法。
⑤ 調用Bean的Setter方法,設置Bean屬性值。
⑥ 若Bean實現了BeanNameAware接口,調用其setBeanName方法,設置BeanName到Bean中。
⑦ 與步驟⑥類似,BeanFactoryAware接口,設置BeanFactory。
⑧ 若BeanFactory裝配了BeanPostProcessor實現類,則調用postProcessBeforeInitialization方法。
⑨ 若Bean實現了InitializingBean接口,則調用其afterPropertiesSet方法。
⑩ 執行節點指定的init-method或註解了@PostConstruct的方法。
⑪ 與步驟⑧對應,執行其xxxAfterxxx方法。
⑫ 將bean返回給調用者,對於單實例bean進行緩存,prototype作用域的Bean容器不再管理其生命週期。
⑬ 容器關閉時,對於實現了DisposableBean接口的單實例Bean,調用其destroy方法。
⑭ 調用單實例節點指定的destroy-method或註解了@PreDestroy的方法。
圖中帶☆標記的方法稱爲後處理器,附屬於容器級別的Bean,影響是全局性的,同時也可以註冊多個Bean,分步驟處理,需實現Ordered接口
階段劃分
按流程劃分
可以看到Bean的聲明週期過程階段很多,不容易理解,可將整個過程劃分爲幾類階段:
- 實例化及其周邊:包括步驟①至④,涉及到兩個類——InstantiationAwareBeanPostProcessor和要實例化的Bean類,經此產生待設置屬性值的半成品Bean;
- 屬性設置、對象初始化及其周邊:包含步驟⑤至⑪,設置各種屬性值(包括Bean的成員變量以及BeanName可選、BeanFactory可選),執行對象初始化方法
- 銷燬bean前處理:包括步驟⑬和⑭,進行銷燬Bean之前的清理工作。
按生命週期級別劃分
- Bean自身內部方法:包括構造函數、屬性設置方法、初始化方法和destroy-method方法。
- Bean級生命週期接口方法:包括BeanNameAware、BeanFactoryAware、InitializingBean和DisposableBean接口定義的方法。主要解決個性化處理的問題。
- 容器級聲明週期接口方法:包括InstantiationAwareBeanPostProcessor、BeanPostProcessor接口方法。主要解決容器中某些Bean共性化處理的問題。(後工廠處理器方法也屬於容器級別,在加載配置後執行一次)
一般,爲了實現與Spring框架的解耦,我們不需要關注Bean級生命週期接口及相關方法,使用init-method和destroy-method指定初始化、銷燬前的清理動作能達到和實現接口同樣的效果。但BeanPostProcesor不同,它以插件的形式爲容器提供了對Bean進行後續加工處理的切入點,同時不需要Bean實現接口,在此基礎上,能夠統一擴展Spring的功能。
ApplicationContext管理的Bean生命週期與BeanFactory類似,不同之處在於增加了裝配容器上下文的ApplicationContextAware接口執行,以及在啓動容器時執行的一些後工廠處理器方法,通過實現BeanFactoryPostProcessor接口實現,Spring框架提供的後工廠處理器有CustomEditorConfigurer、PropertyPlaceholderConfigurer等。
配置Bean
XML Schema的配置方式
xmls對文檔所引用的命名空間進行聲明
- 默認命名空間
- 標準。。。。
- 自定義。。。。
命名空間的定義分爲兩個步驟:第一步指定命名空間的名稱;第二步指定命名空間的Schema文檔格式文件的位置,用空格或回車換行進行分隔。
- xmls:aop=“http://www.springframework.org/schema/aop”
- http://www.springframework.org/schema/aop xxx.xsd
Bean基本配置
- id(Optional)可指定多個名字,逗號、分號、空格分隔,不允許多個相同的id
- name(Optional)支持特殊字符,可指定多個,允許相同name(返回bean時返回後面聲明的bean)
- class(未指定id和name時,使用全限定類名爲名稱。出現多個時,第一個爲全限定類名,第二個其後綴爲 #1、第三個後綴爲#2,匿名bean:爲外層bean提供注入值使用)
命名:id需要滿足XML命名規範,name不限制特殊字符;id和name都可以指定多個名字;id在配置中唯一,name可以有相同的,getBean返回後面聲明的Bean;儘量使用id。
依賴注入
- 屬性注入
要求Bean提供默認構造函數、Setter方法
JavaBean關於屬性命名的特殊規範:變量前兩個字母要麼全部大寫,要不全部小寫。 - 構造函數注入
按類型匹配入參
按索引匹配入參
聯合使用類型和索引匹配入參
自身類型反射匹配入參
循環依賴問題 - 工廠方法注入
非靜態工廠方法:factory-bean factory-method,需要聲明工廠類Bean
靜態工廠方法:class屬性指定工廠類,factory-method指定工廠方法。
注入方式選擇?
構造方法
- 可以保證一些重要屬性在Bean實例化時就設置好,避免因爲一些重要屬性沒有提供導致無用Bean的情況
- 不需要爲每個屬性提供Setter方法,減少類的方法個數
- 更好的封裝類變量,不需要提供Setter方法,避免錯誤調用
屬性注入
- 屬性衆多,構造方法簽名過長,可讀性差
- 構造方法靈活性不夠,屬性可選時需傳null
- 多個構造器配置時需要考慮匹配歧義的問題,配置相對複雜
- 構造方法不利於類的繼承和擴展,子類需引用父類複雜構造方法
- 構造方法注入有時會造成循環依賴的問題。
注入參數
- 字面值
基本數據類型、字符串(可自定義編輯器),property標籤及value子標籤 - 引用其他bean
property標籤及ref子標籤,ref標籤的bean指定bean,parent屬性引用父容器中的bean - 內部bean
- null值
- 級聯屬性
- 集合類型
List、Set、Map、Properties、強類型集合、通過父子bean實現集合合併
簡化配置方式
- 字面值屬性 proverty/constructor-arg/map-entry value
- 引用對象屬性
- 使用p命名空間
定義集合類型的Bean,使用util命名空間
自動配置autowire
- byName
- byType
- constructor
- autodetect
方法注入
- lookup方法注入
通過一個singleton Bean獲取一個prototype - 方法替換
MethodReplacer接口
Bean之間關係
- 繼承
- 依賴
- 引用
Bean作用域
- singleton
- prototype
- Web應用環境相關
- request
- session
- global session
使用註解
- @Autowired(required)
標註屬性
標註方法
集合類標註 - @Qualifier指定名稱
- @Lazy
- 支持標準註解:@Resource、@Inject
Resource按名稱匹配注入Bean,默認用變量名或方法名注入
Inject按類型,但沒有required屬性 - @scope
- @PostConstruct和@PreDestroy(可多個)
基於Java類的配置
@Configuration
@Bean
混合配置
- xml引用Java類配置,通過context:component-scan實現
- Java類配置引用xml,通過@ImportResource實現
Spring容器工作機制
在討論Bean生命週期的時候,涉及到了Spring容器的很多接口及組件,這些是何時實例化的?Spring容器啓動時到底經過了哪些步驟?Bean的初始化過程和容器啓動過程是怎樣銜接的?這些問題從AbstractApplicationContext的refresh方法中窺探一二。
上圖整理了Spring容器啓動時經歷的各個階段,從配置文件一個個節點到容器中完備的Bean實例,可以分爲以下幾個大的階段:
- 配置文件加工,產出爲註冊於BeanDefinitionRegistry中的BeanDefinition成品組件;
- 註冊特殊Bean,基於BeanDefinition,可識別出特殊的一些Bean,包括後處理器、國際化、事件監聽相關Bean等;
- 初始化除了Lazy標記的所有單實例Bean;
- 發佈上下文刷新事件。
《精通Spring4.x》書中對於BeanDefinition、屬性編輯器、工廠後處理器、InstantiationStrategy、BeanWapper等組件有詳細的介紹,在圖中進行了簡短的梳理,有興趣的可以找書來讀一讀。
小結
本文根據《精通Spring4.x 企業應用開發實戰》,總結了SpringIoC的方方面面,從IoC的基本概念出發,介紹SpringIoC的底層Java基礎技術,即反射,介紹了配置Bean的方式以及Spring容器的工作機制及Bean的生命週期階段,希望對讀者瞭解Spring容器、Bean相關配置、內部工作機制有所幫助。
掃描文末二維碼,回覆關鍵字"SpringIoC"獲取總結的完整思維導圖: