Spring底層原理深入分析
bean生命周期
userService.class--->推斷構(gòu)造方法--->對象--->依賴注入--->初始化前(@postConstruct)--->初始化(@afterPropertiesSet)--->初始化后(AOP)--->放入Map(單例池)--->Bean對象
推斷構(gòu)造方法的底層原理
1、使用哪個構(gòu)造方法
@Component public class OrderService { private UserService userService; @Override public OrderService (UserService userService) { this.userService = userService; } @Override public void test) { System.out.println(userService); } }
在上述例子中,因為寫了一個有參構(gòu)造方法,所以無參構(gòu)造方法不能用了。
這個時候在userService屬性上面沒有加@Autowired注解,但是打印發(fā)現(xiàn)這個userService對象存在。
orderService是一個bean,spring想去創(chuàng)造這個bean,就要去用構(gòu)造方法,發(fā)現(xiàn)構(gòu)造方法是有參的,就回去找一個userService對象賦給這個屬性。
當(dāng)加上無參構(gòu)造方法之后,spring就會去用無參的構(gòu)造方法,這時候userService沒有值;當(dāng)有多個構(gòu)造方法的時候,沒有明確告知的情況下(告知是用@Autowired注解),spring會去找無參的構(gòu)造方法,如果沒有無參構(gòu)造方法就直接報錯。
對于第一種情況:“orderService是一個bean,spring想去創(chuàng)造這個bean,就要去用構(gòu)造方法,發(fā)現(xiàn)構(gòu)造方法是有參的,就回去找一個userService對象賦給這個屬性。”的userService對象spring是從哪里找出來賦值給屬性的呢?
spring首先會去單例池根據(jù)beanName即userService找有沒有相應(yīng)的bean對象,如果有就直接賦值給屬性。如果沒有,就去創(chuàng)建(前提是orderService是一個bean,但是沒來得及創(chuàng)建,但如果是多例bean就直接去創(chuàng)建)。
但是如果是去創(chuàng)建的話就有可能出現(xiàn)循環(huán)依賴,考慮在userService中有orderService屬性,并有一個有參構(gòu)造方法:
@Component public class UserService{ private OrderService orderService ; @Override public UserService(OrderService orderService ) { this.orderService = orderService ; } @Override public void test) { System.out.println(orderService ); } }
這時候在創(chuàng)建orderService時需要用到有參構(gòu)造方法,因為沒有userService,這時候就要去創(chuàng)建,創(chuàng)建userService就要用到構(gòu)造方法,可是完蛋,這是又需要orderService,可是orderService本身就在創(chuàng)建,也就是發(fā)生了循環(huán)依賴。
2、如果有參把哪個bean對象賦值給入?yún)?/h3>
假設(shè)單例池中有userService對象,可以直接拿出來用,但是怎么在單例池中拿到這個bean呢?因為參數(shù)名是可以隨意設(shè)定的,所以不能直接拿參數(shù)名去找,所以要根據(jù)類型去單例池找,如果只有一個該類型的bean對象,直接賦值。但是有可能在單例池中存在多個同類型的對象(不同的beanName),這時候再根據(jù)參數(shù)名去匹配,如果找到了就直接賦值,匹配不上就報錯。(先byType,再byName)
AOP實現(xiàn)原理
開啟AOP動態(tài)代理之后,原本例子中的userService沒有值了,因為AOP是發(fā)生在初始化之后,而初始化之后拿到的動態(tài)代理對象是不會去再去做依賴注入,直接放入了單例池,所以即使屬性上面有@Autowired注解也沒用。
cglib是基于父子類實現(xiàn)的,代理對象實質(zhì)是繼承了普通對象,并且代理對象中會有一個普通對象的屬性、以及被增強的方法,在被增強方法中會先執(zhí)行切面邏輯,再執(zhí)行普通對象的方法,而普通對象中是有值的
spring事務(wù)
根據(jù)上面的AOP實現(xiàn),事務(wù)是基于AOP的實現(xiàn),生成的是代理類,如果有@Transactional就開啟spring事務(wù)切面:
1、事務(wù)管理器會新建數(shù)據(jù)庫連接,并且設(shè)置conn.autocommit = false,因為不管是mybatis還是jdbctemplate都是自動提交,這樣就算出現(xiàn)異常,也已經(jīng)提交了。在新建之后,當(dāng)target即普通對象去執(zhí)行test方法市,不管是mybatis還是jdbctemplate操作數(shù)據(jù)庫都要拿到這個連接才能執(zhí)行sql
2、如果執(zhí)行完沒有拋異常就執(zhí)行conn.commit
3、
在a方法上的注解加了never,原本應(yīng)該是要拋出異常的,但是還是順利寫進了數(shù)據(jù)庫,原因是執(zhí)行a方法的還是userService的普通對象(沒有經(jīng)過AOP增強的對象),就識別不了注解。為什么第一個test方法可以識別?因為一開始是被spring管理的bean對象userService執(zhí)行,會有相應(yīng)的邏輯代碼去識別注解,識別到注解后生成了代理類和代理對象,然后去運行的test方法,但是執(zhí)行a方法的時候相當(dāng)于是 new userService,沒有對應(yīng)的邏輯代碼去識別注解。
解決辦法:把userService拆出一個新的類,把a方法寫進新類
@Configuration
一開始沒有加@configuration注解回滾失敗。
jdbcTemplate是拿事務(wù)管理器新建的數(shù)據(jù)庫連接conn。jdbcTemplate是通過ThreadLocal<Map<DataSource, conn>,線程可能會執(zhí)行很多方法,可能會有執(zhí)行不同的datasource,所以是一個map。
因為語法邏輯中jdbcTemplate和事務(wù)管理器中是返回新new出來的datasource對象,這樣如果沒有@configuration,那么jdbcTemplate和事務(wù)管理器拿到的是兩個不同的datasource對象,那么jdbcTemplate去Map里面找不到對應(yīng)的conn,只能自己創(chuàng)建新的連接,這樣就不能被spring事務(wù)管理。
而如果加上了@configuration,那么AppConfig會基于動態(tài)代理產(chǎn)生AppConfig代理對象
AppConfig代理對象會先執(zhí)行自己的代理邏輯,然后去執(zhí)行普通對象的jdbcTemplate方法,進到父類的jdbcTemplate方法后會執(zhí)行dataSource方法,但是都是代理對象在執(zhí)行。代理對象執(zhí)行dataSource方法的時候先執(zhí)行代理邏輯:先去spring容器有沒有dataSource這個bean,如果沒有就創(chuàng)建,如果有就直接返回。
那么就能拿到一樣的datasource對象。
循環(huán)依賴
為什么會出現(xiàn)循環(huán)依賴
首先上面這個例子考慮打破循環(huán)依賴。
可以添加一個map<"對象名",對象>,并把實例化AService得到的普通對象放入這個map中,這樣在B填充A屬性的時候就把AService普通對象注入,B就可以完成創(chuàng)建并放入了單例池,A也就能把單例池中的B對象注入。
但是存在的問題是如果AService在初始化后需要進行AOP,那么最終放入單例池里面的會是AService的代理對象,但是BService拿到的是AService普通對象,因為AService是單例bean,所以只能有一個對象在單例池中,又因為進行了AOP,所以只能是AService的代理對象,并且在其他地方如果依賴了AService,那么應(yīng)該拿到的是AService的代理對象。
提前AOP
解決方法上述打破循環(huán)依賴出現(xiàn)的問題的方法是在把AOP提前,讓B創(chuàng)建注入A屬性的時候拿到的是AService的代理對象,即提前AOP。如果出現(xiàn)了循環(huán)依賴,那么就提前AOP,否則還是在初始化后進行AOP。
如何判斷出現(xiàn)了循環(huán)依賴?創(chuàng)建一個creatingSet<beanName>,放入正在創(chuàng)建的bean的名稱,代表該bean正在創(chuàng)建,后續(xù)可以在屬性注入的步驟中,如果在單例池中找不到對應(yīng)的bean對象而在creatingSet中找到了,就可以判定出現(xiàn)了循環(huán)依賴。
在判定出現(xiàn)循環(huán)依賴之后進行了提前AOP,那么應(yīng)該什么時候創(chuàng)建AService的代理對象使得BService注入屬性的時候拿到的是AService的代理并放入單例池呢?跳到二級緩存
第一級緩存singletonObjects
即單例池
第二級緩存earlySingletonObjects
二級緩存的作用是為了保證單例性:用于出現(xiàn)循環(huán)依賴的情況下,會提前產(chǎn)生一個沒有經(jīng)過完整生命周期的早期bean對象,并保存在二級緩存中。否則可能多次創(chuàng)建同一個類型的bean對象。
考慮如下例子:在上述例子中AService再加入一個CService屬性,并且在CService也依賴AService屬性
在進行bService的生命周期注入aService時會先去二級緩存中根據(jù)beanName找有沒有aService的bean對象,如果沒有就進行AOP并創(chuàng)建aService的代理對象放入二級緩存。
當(dāng)進行cService的生命周期注入aService時就去二級緩存中找,發(fā)現(xiàn)已經(jīng)有了aService,只可以直接取得
但是二級緩存中存放的不是完整的生命周期的bean對象,所以完成屬性填充等動作之后從二級緩存中拿到aService的代理對象放入單例池中。
這時候就不需要在第四步進行AOP了,并且因為AOP的實質(zhì)是在原有bean的基礎(chǔ)上加入切面邏輯,并且AOP后生成的代理對象中還是會有target普通對象
所以在進行屬性填充等動作的時候還是對AService的普通對象進行的,那么代理的對象中的普通對象還是可以拿到這些屬性值,就相當(dāng)于代理對象也拿到了
第三級緩存singletonFactories
打破循環(huán)依賴的關(guān)鍵,類似于上面提及的map,只是在spring的實現(xiàn)中key值是beanName,value是一個lamda表達式,三級緩存中不去判斷是否出現(xiàn)循環(huán)依賴,而是只要是支持循環(huán)依賴并且是單例的,那么就會加入三級緩存
而lamda表達式返回的是一個對象,執(zhí)行l(wèi)amda方法的時候就執(zhí)行了aop,所以返回的是一個代理對象
在底層源碼中,通過第三級緩存來控制第四步中是否需要AOP
如果第三級緩存的map中remove出來是null,整明沒有循壞依賴,就這時候進行AOP并返回增強后的代理對象,反之整明之前已經(jīng)進行了AOP,不需要再進行AOP,直接返回普通對象。
到此這篇關(guān)于Spring底層原理深入分析的文章就介紹到這了,更多相關(guān)Spring底層原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
解析整合mybatis-spring需要的maven依賴配置問題
這篇文章主要介紹了整合mybatis-spring需要的maven依賴配置問題,創(chuàng)建Maven項目,導(dǎo)入相關(guān)jar包,文中還給大家提到了,解決maven靜態(tài)資源約定大于習(xí)慣問題,本文給大家介紹的非常詳細,需要的朋友參考下吧2021-11-11Java高效數(shù)據(jù)傳輸通過綁定快速將數(shù)據(jù)導(dǎo)出至Excel
這篇文章主要介紹了Java高效數(shù)據(jù)傳輸之通過綁定快速將數(shù)據(jù)導(dǎo)出至Excel方法實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11JAVA中使用FileWriter寫數(shù)據(jù)到文本文件步驟詳解
這篇文章主要介紹了JAVA中使用FileWriter寫數(shù)據(jù)到文本文件步驟詳解,FileWriter類提供了多種寫入字符的方法,包括寫入單個字符、寫入字符數(shù)組和寫入字符串等,它還提供了一些其他的方法,如刷新緩沖區(qū)、關(guān)閉文件等,需要的朋友可以參考下2023-10-10SpringBoot多數(shù)據(jù)源配置詳細教程(JdbcTemplate、mybatis)
這篇文章主要介紹了SpringBoot多數(shù)據(jù)源配置詳細教程(JdbcTemplate、mybatis),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03