Spring入門系列:淺析知識點

2023-04-10 12:00:21

前言

講解Spring之前,我們首先梳理下Spring有哪些知識點可以進行入手原始碼分析,比如:

  1. Spring IOC依賴注入
  2. Spring AOP切面程式設計
  3. Spring Bean的宣告週期底層原理
  4. Spring 初始化底層原理
  5. Spring Transaction事務底層原理

Hello World

通過這些知識點,後續我們慢慢在深入Spring的使用及原理剖析,為了更好地理解Spring,我們需要先了解一個最簡單的範例——Hello World。在學習任何框架和語言之前,Hello World都是必不可少的。

//在以前大家都是spring.xml進行注入bean後供Spring框架解析
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

spring.xml中的內容為:

<context:component-scan base-package="com.zhouyu"/>
<bean id="userService" class="com.zhouyu.service.UserService"/>

如果對上面的程式碼或者xml形式很陌生,再看下面一種程式碼,也是目前流行的一種形式

//通過我們的設定類進行注入bean並解析
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MySpringConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test()

MySpringConfig中的內容為:

@ComponentScan("com.xiaoyu")
public class MySpringConfig {
	@Bean
	public UserService userService(){
		return new UserService();
	}
}

相信很多人都會對上面的程式碼不陌生,那我們來看下這三行程式碼都做了那些工作:

  1. 構造一個ClassPathXmlApplicationContext類解析組態檔 或者AnnotationConfigApplicationContext類 解析設定類,那麼呼叫該構造方法除開會範例化得到一個物件,還會做哪些事情?
  2. 從context中獲取一個名字為"userService"的userService物件,那麼為什麼輸入一個字串就可以得到物件呢,好像跟Map<String,Object>有些類似,getBean()又是如何實現的?返回的UserService物件和我們自己直接new的UserService物件有區別嗎?
  3. 通過獲取到的UserService物件呼叫test方法,這個不難理解。

雖然我們目前很少直接使用這種方式來使用Spring,而是使用Spring MVC或者Spring Boot,但是它們都是基於上面這種方式的,都需要在內部去建立一個
ApplicationContext的,只不過:

  1. Spring MVC建立的是XmlWebApplicationContext,和ClassPathXmlApplicationContext類似,都是基於XML設定的
  2. Spring Boot建立的是AnnotationConfigApplicationContext

下面我們將重點講解Spring對bean的建立,也就是大家常說的Bean的生命週期。雖然我們只是入門級別的學習,但我們仍將深入探討流程,但不會涉及到具體的原始碼分析。在後續的原始碼分析系列中,我們將著重分析每一個知識點。

Bean的建立過程

那麼,Spring是如何建立一個Bean的呢?這就是Bean的生命週期。其大體過程如下:

  1. 首先當我們的物件類被設定類使用@Bean又或者被包路徑掃描到將會進入建立Bean的流程
  2. Spring會使用物件構造器進行範例化建立個物件
  3. 開始解析物件的屬性是否有被@autowired註解修飾,如果有會從Spring的單例池中獲取物件並進行注入就是屬性賦值(依賴注入)
  4. 依賴注入後會判斷物件時否實現了各種Aware介面,如:BeanNameAware介面、 BeanClassLoaderAware介面、BeanFactoryAware介面等,並自動呼叫其中的需要被實現的方法(Aware回撥)
  5. 回撥方法實現後,Spring會判斷是否有包含了@postconstruct註解方法,如果有會進行呼叫此方法(範例化前)
  6. Spring判斷物件是否實現了InitializingBean介面,範例化物件就必須呼叫afterPropertiesSet()方法,也是Spring幫呼叫的(初始化)
  7. 最後,Spring會判斷我們的物件是否需要進行AOP,如果不需要那麼Bean到此就完事,如果需要,Spring還會自動動態代理並生成一個代理物件做為Bean(初始化後)

通過以上步驟,我們可以瞭解到建立的物件可能存在兩種結果。如果不需要AOP,Bean就是通過構造方法建立的物件;如果需要AOP,Bean就是代理類所範例化得到的物件,而不是構造方法所得到的物件。

Bean建立後:

  1. 如果當前Bean是單例的(預設),Spring在初始化Bean之後,會將當前已經初始化之後的物件放入Spring管理的單例快取池中(Map結構),這樣如果其他物件需要注入這個物件時,會直接從單例快取池中取出來,進行屬性注入。
  2. 如果當前Bean是多例的(即被@Scope("prototype")註解修飾),每次獲取物件時,或者被其他物件參照時都會重新走一遍建立Bean的邏輯來建立一個新物件。這意味著,每次獲取該Bean時都會建立一個新的範例,而不是從快取池中獲取已有的範例。因此,多例Bean的物件是不共用的,每個物件都是獨立的。

構造器的初始化

在Spring中,每個物件都會有預設的構造器。但是在實際業務中,有時候會存在多個構造器的情況。那麼,Spring如何去選擇使用哪個構造器去建立物件呢?
Spring的判斷邏輯如下:

  1. 如果當前bean有且僅有一個的構造器,那麼直接使用這個構造器建立物件。不管它是有參還是無參。
  2. 如果存在多個構造器,Spring會從中選擇一個無參構造器進行建立物件,如果沒有無參構造器,那麼直接報錯。

Spring的設計思想是這樣的:

  1. 如果只有一個構造器,那麼沒有選擇,只能使用這個構造器

  2. 如果有多個,只選擇沒有入參的構造器,因為無參構造方法本身表示了一種預設的意義

  3. 還要一種就是使用了@Autowired註解修飾,那麼就表示人工干預 了Spring選擇的權利,直接選擇程式設計師指定的構造器,如果有參,裡面的引數bean物件(單例)會從單例快取池中取。

    a.先按照bean型別進行查詢,如果只找到一個範例,那麼直接注入。
    b.如果找到多個範例,那麼會進行匹配入參name名字來確定唯一一個範例。
    c如果沒有找到,則會報錯,無法建立當前Bean物件

綜上所述,Spring會根據Bean的構造器情況進行選擇,如果需要人工干預,可以使用@Autowired註解修飾。

AOP的大致流程

在建立物件時,Spring會判斷當前物件是否需要進行AOP代理。為了確定當前Bean物件是否需要代理,大致流程如下:

  1. Spring啟動時尋找所有使用@AspectJ註解的切面Bean物件
  2. 搜尋切面bean的各個方法是否有包含了@Before、@After、@Around註解。
  3. 檢查當前Bean或方法是否符合我們編寫的Pointcut切面條件。
  4. Spring建立代理物件通常採用cglib代理,JDK代理是另一種模式。後續我們會詳細分析此邏輯。

具體流程如下:

  1. 首先當前bean被確認為需要進行代理,將會生成BeanProxy物件,而不是我們構造出來的Bean。
  2. 當前BeanProxy會重寫符合切面方法的方法
  3. 當前BeanProxy被依賴注入時,給其他Bean的屬性賦值時也會是代理物件。
  4. BeanProxy代理物件都有一個target屬性,用於注入被代理物件,即我們正常構造並進行屬性注入的Bean物件
  5. 當BeanProxy的切面方法被呼叫時,首先呼叫我們編寫的切面邏輯,然後呼叫被代理物件的方法(即我們編寫的業務方法)。這裡也不太準確,具體得看你寫的什麼樣的切面註解,不過大致都是這樣來呼叫的鏈路。

Spring事務

一談到事務,大家首先想到的肯定是@Transaction註解,而這種註解也會被Spring建立物件時檢測到,然後會生成代理物件,這種方式其實工作中用到的也特別多,用起來也特別爽,那我們也大概講下事務的邏輯處理吧,其大致流程如下:

  1. 首先如果當前Bean有@Transaction註解,那麼當前bean會成為代理物件。
  2. 當被呼叫到具體的方法時。Spring則會利用事務管理器TransactionManage獲取一個資料庫連線。
  3. 直接將當前資料庫連線的自動提交關閉,autocommit=false。
  4. 開始執行我們的業務方法,執行業務SQL操作。
  5. 如果執行完方法後,沒有異常則提交事務,如果出現異常則進行回滾。

以上是簡單的事務處理流程,深入細節會涉及到Spring的事務傳播級別,如果現在說的話,會陷入不必要的思考,自此我們的 [Spring入門系列] 也結篇了,在接下來的 [Spring原始碼系列] 中,我將更加詳細地講解這些內容。

「準備開車,可坐穩了別被摔下來」

先對這些流程有個印象,並帶著疑惑來進一步探究Spring的內部機制,好了,今天就講到這裡,我是小雨,我們下期再見。