首頁 > 軟體

Spring底層原理深入分析

2022-07-11 18:03:11

bean生命週期

userService.class--->推斷構造方法--->物件--->依賴注入--->初始化前(@postConstruct)--->初始化(@afterPropertiesSet)--->初始化後(AOP)--->放入Map(單例池)--->Bean物件

推斷構造方法的底層原理

1、使用哪個構造方法

@Component
public class OrderService {
    private UserService userService;
    @Override
    public OrderService (UserService userService) {
       this.userService =  userService;
    }
    @Override
    public void test) {
        System.out.println(userService);
    }
}

在上述例子中,因為寫了一個有參構造方法,所以無參構造方法不能用了。

這個時候在userService屬性上面沒有加@Autowired註解,但是列印發現這個userService物件存在。

orderService是一個bean,spring想去創造這個bean,就要去用構造方法,發現構造方法是有參的,就回去找一個userService物件賦給這個屬性。

當加上無參構造方法之後,spring就會去用無參的構造方法,這時候userService沒有值;當有多個構造方法的時候,沒有明確告知的情況下(告知是用@Autowired註解),spring會去找無參的構造方法,如果沒有無參構造方法就直接報錯。

對於第一種情況:“orderService是一個bean,spring想去創造這個bean,就要去用構造方法,發現構造方法是有參的,就回去找一個userService物件賦給這個屬性。”的userService物件spring是從哪裡找出來賦值給屬性的呢?

spring首先會去單例池根據beanName即userService找有沒有相應的bean物件,如果有就直接賦值給屬性。如果沒有,就去建立(前提是orderService是一個bean,但是沒來得及建立,但如果是多例bean就直接去建立)。

但是如果是去建立的話就有可能出現迴圈依賴,考慮在userService中有orderService屬性,並有一個有參構造方法:

@Component
public class UserService{
    private OrderService orderService ;
    @Override
    public UserService(OrderService orderService ) {
       this.orderService =  orderService ;
    }
    @Override
    public void test) {
        System.out.println(orderService );
    }
}

這時候在建立orderService時需要用到有參構造方法,因為沒有userService,這時候就要去建立,建立userService就要用到構造方法,可是完蛋,這是又需要orderService,可是orderService本身就在建立,也就是發生了迴圈依賴。

2、如果有參把哪個bean物件賦值給入參

假設單例池中有userService物件,可以直接拿出來用,但是怎麼在單例池中拿到這個bean呢?因為引數名是可以隨意設定的,所以不能直接拿引數名去找,所以要根據型別去單例池找,如果只有一個該型別的bean物件,直接賦值。但是有可能在單例池中存在多個同型別的物件(不同的beanName),這時候再根據引數名去匹配,如果找到了就直接賦值,匹配不上就報錯。(先byType,再byName)

AOP實現原理

開啟AOP動態代理之後,原本例子中的userService沒有值了,因為AOP是發生在初始化之後,而初始化之後拿到的動態代理物件是不會去再去做依賴注入,直接放入了單例池,所以即使屬性上面有@Autowired註解也沒用。

cglib是基於父子類實現的,代理物件實質是繼承了普通物件,並且代理物件中會有一個普通物件的屬性、以及被增強的方法,在被增強方法中會先執行切面邏輯,再執行普通物件的方法,而普通物件中是有值的

spring事務

根據上面的AOP實現,事務是基於AOP的實現,生成的是代理類,如果有@Transactional就開啟spring事務切面:

1、事務管理器會新建資料庫連線,並且設定conn.autocommit = false,因為不管是mybatis還是jdbctemplate都是自動提交,這樣就算出現異常,也已經提交了。在新建之後,當target即普通物件去執行test方法市,不管是mybatis還是jdbctemplate運算元據庫都要拿到這個連線才能執行sql

2、如果執行完沒有拋異常就執行conn.commit

3、

在a方法上的註解加了never,原本應該是要丟擲異常的,但是還是順利寫進了資料庫,原因是執行a方法的還是userService的普通物件(沒有經過AOP增強的物件),就識別不了註解。為什麼第一個test方法可以識別?因為一開始是被spring管理的bean物件userService執行,會有相應的邏輯程式碼去識別註解,識別到註解後生成了代理類和代理物件,然後去執行的test方法,但是執行a方法的時候相當於是 new userService,沒有對應的邏輯程式碼去識別註解。

解決辦法:把userService拆出一個新的類,把a方法寫進新類

@Configuration

一開始沒有加@configuration註解回滾失敗。

jdbcTemplate是拿事務管理器新建的資料庫連線conn。jdbcTemplate是通過ThreadLocal<Map<DataSource, conn>,執行緒可能會執行很多方法,可能會有執行不同的datasource,所以是一個map。

因為語法邏輯中jdbcTemplate和事務管理器中是返回新new出來的datasource物件,這樣如果沒有@configuration,那麼jdbcTemplate和事務管理器拿到的是兩個不同的datasource物件,那麼jdbcTemplate去Map裡面找不到對應的conn,只能自己建立新的連線,這樣就不能被spring事務管理。

而如果加上了@configuration,那麼AppConfig會基於動態代理產生AppConfig代理物件

AppConfig代理物件會先執行自己的代理邏輯,然後去執行普通物件的jdbcTemplate方法,進到父類別的jdbcTemplate方法後會執行dataSource方法,但是都是代理物件在執行。代理物件執行dataSource方法的時候先執行代理邏輯:先去spring容器有沒有dataSource這個bean,如果沒有就建立,如果有就直接返回。

那麼就能拿到一樣的datasource物件。

迴圈依賴

為什麼會出現迴圈依賴

首先上面這個例子考慮打破迴圈依賴。

可以新增一個map<"物件名",物件>,並把範例化AService得到的普通物件放入這個map中,這樣在B填充A屬性的時候就把AService普通物件注入,B就可以完成建立並放入了單例池,A也就能把單例池中的B物件注入。

但是存在的問題是如果AService在初始化後需要進行AOP,那麼最終放入單例池裡面的會是AService的代理物件,但是BService拿到的是AService普通物件,因為AService是單例bean,所以只能有一個物件在單例池中,又因為進行了AOP,所以只能是AService的代理物件,並且在其他地方如果依賴了AService,那麼應該拿到的是AService的代理物件。

提前AOP

解決方法上述打破迴圈依賴出現的問題的方法是在把AOP提前,讓B建立注入A屬性的時候拿到的是AService的代理物件,即提前AOP。如果出現了迴圈依賴,那麼就提前AOP,否則還是在初始化後進行AOP。

如何判斷出現了迴圈依賴?建立一個creatingSet<beanName>,放入正在建立的bean的名稱,代表該bean正在建立,後續可以在屬性注入的步驟中,如果在單例池中找不到對應的bean物件而在creatingSet中找到了,就可以判定出現了迴圈依賴。

在判定出現迴圈依賴之後進行了提前AOP,那麼應該什麼時候建立AService的代理物件使得BService注入屬性的時候拿到的是AService的代理並放入單例池呢?跳到二級快取

第一級快取singletonObjects

即單例池

第二級快取earlySingletonObjects

二級快取的作用是為了保證單例性:用於出現迴圈依賴的情況下,會提前產生一個沒有經過完整生命週期的早期bean物件,並儲存在二級快取中。否則可能多次建立同一個型別的bean物件。

考慮如下例子:在上述例子中AService再加入一個CService屬性,並且在CService也依賴AService屬性

在進行bService的生命週期注入aService時會先去二級快取中根據beanName找有沒有aService的bean物件,如果沒有就進行AOP並建立aService的代理物件放入二級快取。

當進行cService的生命週期注入aService時就去二級快取中找,發現已經有了aService,只可以直接取得

但是二級快取中存放的不是完整的生命週期的bean物件,所以完成屬性填充等動作之後從二級快取中拿到aService的代理物件放入單例池中。

這時候就不需要在第四步進行AOP了,並且因為AOP的實質是在原有bean的基礎上加入切面邏輯,並且AOP後生成的代理物件中還是會有target普通物件

所以在進行屬性填充等動作的時候還是對AService的普通物件進行的,那麼代理的物件中的普通物件還是可以拿到這些屬性值,就相當於代理物件也拿到了

第三級快取singletonFactories

打破迴圈依賴的關鍵,類似於上面提及的map,只是在spring的實現中key值是beanName,value是一個lamda表示式,三級快取中不去判斷是否出現迴圈依賴,而是隻要是支援迴圈依賴並且是單例的,那麼就會加入三級快取

而lamda表示式返回的是一個物件,執行lamda方法的時候就執行了aop,所以返回的是一個代理物件

在底層原始碼中,通過第三級快取來控制第四步中是否需要AOP

如果第三級快取的map中remove出來是null,整明沒有循壞依賴,就這時候進行AOP並返回增強後的代理物件,反之整明之前已經進行了AOP,不需要再進行AOP,直接返回普通物件。

到此這篇關於Spring底層原理深入分析的文章就介紹到這了,更多相關Spring底層原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com