《精通Spring4.x》閱讀筆記(一)- SpringIoC讀這一篇就夠了

文末福利:掃描文末二維碼,回覆關鍵字"SpringIoC"獲取總結的完整思維導圖。

Spring4.x-IoC

IoC基本概念

IoC(Inverse of Control 控制反轉):接口實現類的選擇控制權,從調用類中移除,轉交給第三方決定,即由Spring容器藉由Bean配置來進行控制。

IoC的概念不太直觀,後來有人提出DI的概念用來代替IoC。

DI(Dependency Injection 依賴注入):調用類對某一接口的實現類的依賴關係由第三方(容器或協作類)注入,以移除調用類對某一接口實現類的依賴。

IoC的類型有:

  1. 構造函數注入
  2. 屬性注入
  3. 接口注入(效果與屬性注入類似,不推薦使用)

Spring支持構造函數注入和屬性注入。

IoC的實現方式–反射

Java語言允許通過程序化的方式間接對Class進行操作。Class文件由類裝載器裝載後,在JVM中將形成描述Class結構的元信息對象,通過該元信息對象可以獲知Class的結構信息。爲使用程序化的方式操作Class對象開闢了途徑。Java反射涉及到的主要類如下:

ClassLoader

類裝載器:負責尋找類的字節碼文件並構造出類在JVM內部表示對象的組件

類裝載步驟:

  1. 裝載:查找和導入Class文件
  2. 鏈接
    ① 校驗:檢查載入Class文件數據的正確性
    ② 準備:給類的靜態變量分配存儲空間
    ③ 解析(可選):將符號引用轉換成直接引用
  3. 初始化:對類的靜態變量、靜態代碼塊執行初始化工作

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);(全盤負責)
  • 先委託父裝載器尋找目標類,找不到的情況下從自己的類路徑中查找並裝載目標類。(委託機制)

主要方法:

  1. defineClass(String name, byte[] b, int off, int len):將類文件的字節數組轉換成JVM內部的java.lang.Class對象。字節數組可以從本地文件系統、遠程網絡獲取。參數name爲字節數組對應的全限定類名。該方法由JVM裝載類時調用構造Class對象
  2. getParent() 獲取裝載器的父裝載器。除根裝載器,所有類裝載器有且僅有一個父裝載器。ExtClassLoader的父裝載器是根裝載器,非Java語言編寫,所以調用該方法時返回null;
  3. loadClass(name)、loadClass(name, resolve):通過全限定類名裝載類;
  4. findSystemClass(name):從本地文件系統載入Class文件;
  5. findLoadedClass(name):查看ClassLoader是否已經裝載某個類,已裝入會返回Class對象,否則返回null。

Class文件、類裝載器、Class類對象之間的引用關係如圖:
類加載機制

主要的反射類

  1. Constructor:構造函數反射類,可通過Class對象的getContructor(s)方法獲得,其主要的方法是newInstance可創建類對象實例,相當於new關鍵字。
  2. Method:類方法的反射類,可通過Class對象的getDeclaredMethod(s)方法獲得,其主要的方法是invoke執行指定對象的這個方法。同時,可通過其他方法獲得該Method的返回、參數類型、異常、註解信息
  3. Field:類成員變量反射類,可通過Class對象的getDeclaredField(s)方法獲得,其主要方法是set爲指定對象域設置值,基本類型域通過setInt/setBoolean等設置值。

資源訪問與加載

Spring通過訪問配置文件,或者讀取配置類進行容器的初始化、啓動、刷新等操作,資源訪問作爲Spring框架的底層基礎實施,具體是怎樣實現的呢?提供了哪些擴展的方式?下面具體來看……

Spring設計了Resource接口,爲應用提供了更強的底層資源訪問能力。通過Spring接口,可以將Spring的配置信息放置到任何地方,比如數據庫、LDAP等。

Resource在Spring框架中起着不可或缺的作用,Spring使用Resource裝載各種資源,包括配置文件資源、國際化屬性文件資源等。具體實現類如下圖:

資源實現類

資源實現類體系結構

  1. WritableResource:字接口類定義了可寫資源相關方法,其實現類有FileSystemResource、PathResource。

  2. ClassPathResource:類路徑下的資源,資源以類路徑的方式表示。

  3. UrlResource:封裝了java.net.URL,能夠訪問通過URL表示的資源,如文件系統資源、HTTP資源等。

  4. ServletContextResource:爲訪問Web容器上下文中資源定義的類。以Web應用根路徑的方式加載資源。

  5. ByteArrayResource:二進制數組表示的資源。

  6. InputStreamResource:以輸入流返回表示。

  7. FileSystemResource:文件系統資源。

  8. 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的功能,具體如下:

BeanFactory類繼承體系

  1. ListableBeanFactory 定義訪問容器中基本信息的方法:查看個數、獲取某一類型Bean的配置名、容器中是否包含某一Bean等。
  2. HierarchicalBeanFactory 父子級聯IoC容器接口,子容器可以通過該接口方法訪問父容器,而父容器無法訪問子容器。子容器可以擁有一個和父容器id相同的Bean。增強了Spring框架的擴展性和靈活性,第三方可以通過編程方式添加子容器,提供一些額外功能。比如:SpringMVC中,展現層Bean位於一個子容器,可訪問業務層和持久才能夠的bean,反過來卻無法訪問。
  3. ConfigurableBeanFactory 重要接口,增強可定製性。包括:設置類裝載器、屬性編輯器、容器初始化後置處理器等方法。
  4. AutowireCapableBeanFactory 定義將容器中的bean按某種規則(名字或類型匹配)進行自動裝配的方法
  5. SingletonBeanRegistry 允許運行期向容器註冊單實例Bean的方法
  6. 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。類繼承體系爲:
WebApplicationContext類繼承體系

Spring的Web應用上下文與Web容器可以實現互相訪問:
Spring與Web應用上下文融合

WebApplicationContext的初始化方式區別於BeanFactory和ApplicationContext,需要配置在Web容器啓動的基礎上進行啓動,具體是配置ContextLoaderServlet或ContextLoaderListener完成容器的初始化工作。

Bean生命週期

在瞭解了Spring容器核心接口的情況下,繼續討論被Spring容器管理的Bean,具體會經過哪些階段完成初始化,以及銷燬的過程又是怎樣?

Spring初始化Bean經過了若干步驟,具體流程如下圖所示:
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的聲明週期過程階段很多,不容易理解,可將整個過程劃分爲幾類階段:

  1. 實例化及其周邊:包括步驟①至④,涉及到兩個類——InstantiationAwareBeanPostProcessor和要實例化的Bean類,經此產生待設置屬性值的半成品Bean;
  2. 屬性設置、對象初始化及其周邊:包含步驟⑤至⑪,設置各種屬性值(包括Bean的成員變量以及BeanName可選、BeanFactory可選),執行對象初始化方法
  3. 銷燬bean前處理:包括步驟⑬和⑭,進行銷燬Bean之前的清理工作。

按生命週期級別劃分
Bean生命週期

  1. Bean自身內部方法:包括構造函數、屬性設置方法、初始化方法和destroy-method方法。
  2. Bean級生命週期接口方法:包括BeanNameAware、BeanFactoryAware、InitializingBean和DisposableBean接口定義的方法。主要解決個性化處理的問題。
  3. 容器級聲明週期接口方法:包括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文檔格式文件的位置,用空格或回車換行進行分隔。
  1. xmls:aop=“http://www.springframework.org/schema/aop”
  2. 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
  • 多個構造器配置時需要考慮匹配歧義的問題,配置相對複雜
  • 構造方法不利於類的繼承和擴展,子類需引用父類複雜構造方法
  • 構造方法注入有時會造成循環依賴的問題。

注入參數

  1. 字面值
    基本數據類型、字符串(可自定義編輯器),property標籤及value子標籤
  2. 引用其他bean
    property標籤及ref子標籤,ref標籤的bean指定bean,parent屬性引用父容器中的bean
  3. 內部bean
  4. null值
  5. 級聯屬性
  6. 集合類型
    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作用域

  1. singleton
  2. prototype
  3. 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容器工作機制

上圖整理了Spring容器啓動時經歷的各個階段,從配置文件一個個節點到容器中完備的Bean實例,可以分爲以下幾個大的階段:

  1. 配置文件加工,產出爲註冊於BeanDefinitionRegistry中的BeanDefinition成品組件;
  2. 註冊特殊Bean,基於BeanDefinition,可識別出特殊的一些Bean,包括後處理器、國際化、事件監聽相關Bean等;
  3. 初始化除了Lazy標記的所有單實例Bean;
  4. 發佈上下文刷新事件。

《精通Spring4.x》書中對於BeanDefinition、屬性編輯器、工廠後處理器、InstantiationStrategy、BeanWapper等組件有詳細的介紹,在圖中進行了簡短的梳理,有興趣的可以找書來讀一讀。

小結

本文根據《精通Spring4.x 企業應用開發實戰》,總結了SpringIoC的方方面面,從IoC的基本概念出發,介紹SpringIoC的底層Java基礎技術,即反射,介紹了配置Bean的方式以及Spring容器的工作機制及Bean的生命週期階段,希望對讀者瞭解Spring容器、Bean相關配置、內部工作機制有所幫助。

掃描文末二維碼,回覆關鍵字"SpringIoC"獲取總結的完整思維導圖:
我的公衆號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章