<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
userService.class--->推斷構造方法--->物件--->依賴注入--->初始化前(@postConstruct)--->初始化(@afterPropertiesSet)--->初始化後(AOP)--->放入Map(單例池)--->Bean物件
@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本身就在建立,也就是發生了迴圈依賴。
假設單例池中有userService物件,可以直接拿出來用,但是怎麼在單例池中拿到這個bean呢?因為引數名是可以隨意設定的,所以不能直接拿引數名去找,所以要根據型別去單例池找,如果只有一個該型別的bean物件,直接賦值。但是有可能在單例池中存在多個同型別的物件(不同的beanName),這時候再根據引數名去匹配,如果找到了就直接賦值,匹配不上就報錯。(先byType,再byName)
開啟AOP動態代理之後,原本例子中的userService沒有值了,因為AOP是發生在初始化之後,而初始化之後拿到的動態代理物件是不會去再去做依賴注入,直接放入了單例池,所以即使屬性上面有@Autowired註解也沒用。
cglib是基於父子類實現的,代理物件實質是繼承了普通物件,並且代理物件中會有一個普通物件的屬性、以及被增強的方法,在被增強方法中會先執行切面邏輯,再執行普通物件的方法,而普通物件中是有值的
根據上面的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註解回滾失敗。
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提前,讓B建立注入A屬性的時候拿到的是AService的代理物件,即提前AOP。如果出現了迴圈依賴,那麼就提前AOP,否則還是在初始化後進行AOP。
如何判斷出現了迴圈依賴?建立一個creatingSet<beanName>,放入正在建立的bean的名稱,代表該bean正在建立,後續可以在屬性注入的步驟中,如果在單例池中找不到對應的bean物件而在creatingSet中找到了,就可以判定出現了迴圈依賴。
在判定出現迴圈依賴之後進行了提前AOP,那麼應該什麼時候建立AService的代理物件使得BService注入屬性的時候拿到的是AService的代理並放入單例池呢?跳到二級快取
即單例池
二級快取的作用是為了保證單例性:用於出現迴圈依賴的情況下,會提前產生一個沒有經過完整生命週期的早期bean物件,並儲存在二級快取中。否則可能多次建立同一個型別的bean物件。
考慮如下例子:在上述例子中AService再加入一個CService屬性,並且在CService也依賴AService屬性
在進行bService的生命週期注入aService時會先去二級快取中根據beanName找有沒有aService的bean物件,如果沒有就進行AOP並建立aService的代理物件放入二級快取。
當進行cService的生命週期注入aService時就去二級快取中找,發現已經有了aService,只可以直接取得
但是二級快取中存放的不是完整的生命週期的bean物件,所以完成屬性填充等動作之後從二級快取中拿到aService的代理物件放入單例池中。
這時候就不需要在第四步進行AOP了,並且因為AOP的實質是在原有bean的基礎上加入切面邏輯,並且AOP後生成的代理物件中還是會有target普通物件
所以在進行屬性填充等動作的時候還是對AService的普通物件進行的,那麼代理的物件中的普通物件還是可以拿到這些屬性值,就相當於代理物件也拿到了
打破迴圈依賴的關鍵,類似於上面提及的map,只是在spring的實現中key值是beanName,value是一個lamda表示式,三級快取中不去判斷是否出現迴圈依賴,而是隻要是支援迴圈依賴並且是單例的,那麼就會加入三級快取
而lamda表示式返回的是一個物件,執行lamda方法的時候就執行了aop,所以返回的是一個代理物件
在底層原始碼中,通過第三級快取來控制第四步中是否需要AOP
如果第三級快取的map中remove出來是null,整明沒有循壞依賴,就這時候進行AOP並返回增強後的代理物件,反之整明之前已經進行了AOP,不需要再進行AOP,直接返回普通物件。
到此這篇關於Spring底層原理深入分析的文章就介紹到這了,更多相關Spring底層原理內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45