1.Spring底層核心原理解析

本篇文章會把Spring中核心知識點都給大家進行串講,讓大家對Spring的底層有一個整體的大致瞭解,比如:

  1. Bean的生命週期底層原理
  2. 依賴注入底層原理
  3. 初始化底層原理
  4. 推斷構造方法底層原理
  5. AOP底層原理
  6. Spring事務底層原理

但都只是大致流程,後續系列文章會針對每個流程詳細深入的講解並分析源碼實現

先來看看入門使用Spring的代碼:

ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

對於這三行代碼應該,大部分同學應該都是比較熟悉,這是學習Spring的hello world。可是,這三行代碼底層都做了什麼,比如:

  1. 第一行代碼,會構造一個ClassPathXmlApplicationContext對象,ClassPathXmlApplicationContext該如何理解,調用該構造方法除開會實例化得到一個對象,還會做哪些事情?
  2. 第二行代碼,會調用ClassPathXmlApplicationContext的getBean方法,會得到一個UserService對象,getBean()是如何實現的?返回的UserService對象和我們自己直接new的UserService對象有區別嗎?
  3. 第三行代碼,就是簡單的調用UserService的test()方法,不難理解。

光看這三行代碼,其實並不能體現出來Spring的強大之處,也不能理解爲什麼需要ClassPathXmlApplicationContext和getBean()方法,隨着系列文章的深入將會改變你此時的觀念,而對於上面的這些疑問,也會隨着文章深入逐步得到解決。對於這三行代碼,你現在可以認爲:如果你要用Spring,你就得這麼寫。就像你要用Mybatis,你就得寫各種Mapper接口。

但是用ClassPathXmlApplicationContext其實已經過時了,在新版的Spring MVC和Spring Boot的底層主要用的都是AnnotationConfigApplicationContext,比如:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring.xml");
UserService userService = (UserService) context.getBean("userService");
userService.test();

可以看到AnnotationConfigApplicationContext的用法和ClassPathXmlApplicationContext是非常類似的,只不過需要傳入的是一個class,而不是一個xml文件。 

而AppConfig.class和spring.xml一樣,表示Spring的配置,比如可以指定掃描路徑,可以直接定義Bean,比如:

spring.xml中的內容爲:

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

AppConfig中的內容爲:

@ComponentScan("com.zhouyu")
public class AppConfig {

	@Bean
	public UserService userService(){
		return new UserService();
	}

} 

所以spring.xml和AppConfig.class本質上是一樣的。

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

  1. Spring MVC創建的是XmlWebApplicationContext,和ClassPathXmlApplicationContext類似,都是基於XML配置的
  2. Spring Boot創建的是AnnotationConfigApplicationContext

因爲AnnotationConfigApplicationContext是比較重要的,並且AnnotationConfigApplicationContext和ClassPathXmlApplicationContext大部分底層都是共同的,後續文章我們會着重將AnnotationConfigApplicationContext的底層實現,對於ClassPathXmlApplicationContext,可以查看相關源碼即可。

Spring中是如何創建一個對象?

其實不管是AnnotationConfigApplicationContext還是ClassPathXmlApplicationContext,目前,我們都可以簡單的將它們理解爲就是用來創建Java對象的,比如調用getBean()就會去創建對象(此處不嚴謹,getBean可能也不會去創建對象,後續文章詳解)。

在Java語言中,肯定是根據某個類來創建一個對象的。我們在看一下實例代碼:

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
UserService userService = (UserService) context.getBean("userService");
userService.test();

當我們調用context.getBean("userService")時,就會去創建一個對象,但是getBean方法內部怎麼知道"userService"對應的是UserService類呢?

所以,我們就可以分析出來,在調用AnnotationConfigApplicationContext的構造方法時,也就是第一行代碼,會去做一些事情:

  1. 解析AppConfig.class,得到掃描路徑
  2. 遍歷掃描路徑下的所有Java類,如果發現某個類上存在@Component、@Service等註解,那麼Spring就把這個類記錄下來,存在一個Map中,比如Map<String, Class>。(實際上,Spring源碼中確實存在類似的這麼一個Map,叫做BeanDefinitionMap,後續文章會講到
  3. Spring會根據某個規則生成當前類對應的beanName,作爲key存入Map,當前類作爲value 

這樣,但調用context.getBean("userService")時,就可以根據"userService"找到UserService類,從而就可以去創建對象了。

Bean的創建過程

那麼Spring到底是如何來創建一個Bean的呢,這個就是Bean創建的生命週期,大致過程如下

  1. 利用該類的構造方法來實例化得到一個對象(但是如何一個類中有多個構造方法,Spring則會進行選擇,這個叫做推斷構造方法
  2. 得到一個對象後,Spring會判斷該對象中是否存在被@Autowired註解了的屬性,把這些屬性找出來並由Spring進行賦值(依賴注入
  3. 依賴注入後,Spring會判斷該對象是否實現了BeanNameAware接口、BeanClassLoaderAware接口、BeanFactoryAware接口,如果實現了,就表示當前對象必須實現該接口中所定義的setBeanName()、setBeanClassLoader()、setBeanFactory()方法,那Spring就會調用這些方法並傳入相應的參數(Aware回調
  4. Aware回調後,Spring會判斷該對象中是否存在某個方法被@PostConstruct註解了,如果存在,Spring會調用當前對象的此方法(初始化前
  5. 緊接着,Spring會判斷該對象是否實現了InitializingBean接口,如果實現了,就表示當前對象必須實現該接口中的afterPropertiesSet()方法,那Spring就會調用當前對象中的afterPropertiesSet()方法(初始化
  6. 最後,Spring會判斷當前對象需不需要進行AOP,如果不需要那麼Bean就創建完了,如果需要進行AOP,則會進行動態代理並生成一個代理對象做爲Bean(初始化後

通過最後一步,我們可以發現,當Spring根據UserService類來創建一個Bean時:

  1. 如果不用進行AOP,那麼Bean就是UserService類的構造方法所得到的對象。
  2. 如果需要進行AOP,那麼Bean就是UserService的代理類所實例化得到的對象,而不是UserService本身所得到的對象。

Bean對象創建出來後:

  1. 如果當前Bean是單例Bean,那麼會把該Bean對象存入一個Map<String, Object>,Map的key爲beanName,value爲Bean對象。這樣下次getBean時就可以直接從Map中拿到對應的Bean對象了。(實際上,在Spring源碼中,這個Map就是單例池
  2. 如果當前Bean是原型Bean,那麼後續沒有其他動作,不會存入一個Map,下次getBean時會再次執行上述創建過程,得到一個新的Bean對象。

推斷構造方法

Spring在基於某個類生成Bean的過程中,需要利用該類的構造方法來實例化得到一個對象,但是如果一個類存在多個構造方法,Spring會使用哪個呢?

Spring的判斷邏輯如下:

1. 如果一個類只存在一個構造方法,不管該構造方法是無參構造方法,還是有參構造方法,Spring都會用這個構造方法

2. 如果一個類存在多個構造方法

a. 這些構造方法中,存在一個無參的構造方法,那麼Spring就會用這個無參的構造方法

b. 這些構造方法中,不存在一個無參的構造方法,那麼Spring就會報錯 

Spring的設計思想是這樣的:

1. 如果一個類只有一個構造方法,那麼沒得選擇,只能用這個構造方法

2. 如果一個類存在多個構造方法,Spring不知道如何選擇,就會看是否有無參的構造方法,因爲無參構造方法本身表示了一種默認的意義

3. 不過如果某個構造方法上加了@Autowired註解,那就表示程序員告訴Spring就用這個加了註解的方法,那Spring就會用這個加了@Autowired註解構造方法了

需要重視的是,如果Spring選擇了一個有參的構造方法,Spring在調用這個有參構造方法時,需要傳入參數,那這個參數是怎麼來的呢?

Spring會根據入參的類型和入參的名字去Spring中找Bean對象(以單例Bean爲例,Spring會從單例池那個Map中去找):

  1. 1. 先根據入參類型找,如果只找到一個,那就直接用來作爲入參
  2. 2. 如果根據類型找到多個,則再根據入參名字來確定唯一一個
  3. 3. 最終如果沒有找到,則會報錯,無法創建當前Bean對象

確定用哪個構造方法,確定入參的Bean對象,這個過程就叫做推斷構造方法

AOP大致流程

AOP就是進行動態代理,在創建一個Bean的過程中,Spring在最後一步會去判斷當前正在創建的這個Bean是不是需要進行AOP,如果需要則會進行動態代理。

如何判斷當前Bean對象需不需要進行AOP:

  1. 找出所有的切面Bean
  2. 遍歷切面中的每個方法,看是否寫了@Before、@After等註解
  3. 如果寫了,則判斷所對應的Pointcut是否和當前Bean對象的類是否匹配
  4. 如果匹配則表示當前Bean對象有匹配的的Pointcut,表示需要進行AOP

利用cglib進行AOP的大致流程:

  1. 生成代理類UserServiceProxy,代理類繼承UserService
  2. 代理類中重寫了父類的方法,比如UserService中的test()方法
  3. 代理類中還會有一個target屬性,該屬性的值爲被代理對象(也就是通過UserService類推斷構造方法實例化出來的對象,進行了依賴注入、初始化等步驟的對象)
  4. 代理類中的test()方法被執行時的邏輯如下:

a. 執行切面邏輯(@Before)

b. 調用target.test()

當我們從Spring容器得到UserService的Bean對象時,拿到的就是UserServiceProxy所生成的對象,也就是代理對象。

UserService代理對象.test()--->執行切面邏輯--->target.test(),注意target對象不是代理對象,而是被代理對象。

Spring事務

當我們在某個方法上加了@Transactional註解後,就表示該方法在調用時會開啓Spring事務,而這個方法所在的類所對應的Bean對象會是該類的代理對象。

Spring事務的代理對象執行某個方法時的步驟:

  1. 判斷當前執行的方法是否存在@Transactional註解
  2. 如果存在,則利用事務管理器(TransactionMananger)新建一個數據庫連接
  3. 修改數據庫連接的autocommit爲false
  4. 執行target.test(),執行程序員所寫的業務邏輯代碼,也就是執行sql
  5. 執行完了之後如果沒有出現異常,則提交,否則回滾

Spring事務是否會失效的判斷標準:某個加了@Transactional註解的方法被調用時,要判斷到底是不是直接被代理對象調用的,如果是則事務會生效,如果不是則失效。

 

本系列文章來自圖靈學院周瑜老師整理並分享,本博客可能對內容稍作修改並搬運

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