從一個簡單的例子看spring ApplicationContext上下文隔離
- 2019 年 11 月 7 日
- 筆記
前言
某天,瀏覽部落格園的時候,對首頁上面的一篇文章,標題為:<<一個普通類就能幹趴你的springboot,你信嗎?>>,文章鏈接:https://www.cnblogs.com/rongdi/p/11780204.html#4414216 很是感興趣。點進去之後,大致看一下。該篇博文主要說的是在使用spring boot環境下想創建一個名為Environment的bean,結果發現創建不了,於是不斷調試終於找到了“真理”。
說真的。這篇博文的內容非常長,主要也是記錄調試過程的“流水賬”。我也只看到了看頭,就迅速拉到文章結尾看一下。比較讓我感到震驚的是,部落客提到為了寫這篇文章花費了很長的時間,從該部落客的這篇文章中摘錄了一句話:周五晚上從下班回家一邊一步步斷點一遍寫這篇部落格。可以看出該部落客很是用心,調試程式是一件很費心,耗時的事。於是評論區送上一個大大的贊。
無論是調試還是閱讀Spring源碼,真的是一件很枯燥的事,非常考驗人的耐心,由其是spring發展了這麼多年,已經形成了生態圈。其程式碼也是高度抽象。曾經對spring進行過一番折騰,也是為了給自己所在小團隊提供一個基於spring封裝的迷你型的小框架。
由於有折騰過spring的經歷,我一眼就看出了問題的所在。根本原因就在於spring框架自身也有一個Environment類,在應用程式啟動時也會向spring ApplicationContext中注入名為environment的bean,這樣就會跟部落客命名Environment類注入名為environment的bean產生衝突,因為這兩個bean的名稱一樣。
像BAT這樣級別的公司往往內部或多或少都會有自己的框架,這些框架往往都是由一個類似於基礎架構部的團隊來負責提供的,這樣應用開發小組會基於這個框架快速的開發應用。應用開發者一般只會關心如何使用框架,一般都由專門的人來折騰框架。雖然我沒有在這樣體量的公司裡面工作過,鑒於之前折騰框架經驗來看。框架無論多麼的高大上,有一點是可以肯定的時,框架所使用的資源跟應用所使用的資源肯定會進行隔離。
為什麼會這樣說呢,打個比方。封裝框架的過程中肯定會引入一些第三方的jar,應用在開發的過程也會引用第三方jar,假設框架和應用同時引用一個jar但是二者的版本不同?那可能會導致程式在運行的過程中搞不好就會出現 java.lang.NoSuchMethodException異常,包衝突了。這時該怎麼辦?如果要框架jar跟應用的jar保持一致,那就不得了,這麼多應用都使用框架進行開發,牽一髮而動全身,風險即大。如果應用使用跟框架一樣的jar,但是這個jar又沒有相應的方法,使用不了。可以想像一樣,如果框架和應用沒有分別定義自身的類載入器來載入各自的所引用的jar,遇到這樣的場景,解決起來將會非常棘手。
所以在封裝框架的過程中,都會對框架所引用的資源跟應用所使用的資源都要進行相應的隔離,如果不隔離的話,框架三天兩天就要改動,對應用開發者來說就會認為框架非常的不穩定。由其在BAT這樣大體量的公司,開發人員如此眾多,對框架提供技術支撐的人不會很多,框架如果不夠穩定的話,搞不好那麼那些提供技術支撐人員的電話,每天都會響個不停,會到處去解決問題,疲於奔命。就像那篇文章提到定義Envrionment bean時候,應用就會跑起不來,打電話給技術支撐的人,人家一過來搗鼓一番對像你說,老兄,對不住啊,命名衝突了,換這個名字吧。也許重新命個名字了事,但是有些場景這個類的名字改不了,別人的程式碼已經固定了要使用這個名字來調用你的bean,改名字別人就調不了。重新命名也許可以解決問題,但內心深處,你會對這個框架失去信心了。什麼框架,還限制別人bean的名字。
像spring框架就提供了對ApplicationContext進行隔離的功能,可以輕鬆解決這個問題。在spring官網的文檔中我也沒有看到有提到,不允許應用程式注入一個命名為Environment的Bean。
程式出錯
我已經將復現同樣錯誤的示常式序程式碼上傳到了gitee上面。大家可以把程式碼拉下來,跑起來會出現跟那篇文章中提所到的一模一樣錯誤。 出現這樣錯誤的原因就是由於兩個同名的類注入到同一個ApplicationContext中導致的。
示例程式碼鏈接:https://gitee.com/fiercetiger/laboratory/tree/master/applicationcontext-test
導致程式出錯,Bean的源碼如下所示:
1 @Component 2 public class Environment { 3 }
ApplicationContext隔離
spring ApplicationContext是可以設置成上下級關係的,查找bean的時候如果在當前的ApplicationContext中沒有找到的話,就會到自己的父級的ApplicationContext中去查找,一直向上回溯,如果找到就會返回。這樣一來的話,我們可以這樣處理。讓應用的ApplicationContext作為spring框架的ApplicationContext的父級。示常式序,我也提交到了gitee上面,可以把程式碼拉下來,跑一下就會發現沒有報錯。
示例程式碼鏈接:https://gitee.com/fiercetiger/laboratory/tree/master/applicationcontext-test2
關鍵程式碼如下所示,定義一個類繼承spring boot的SpringApplication類,覆蓋其createApplicationContext方法,在方法中首先創建應用的ApplicationContext,並注入應用所定義的Environment Bean,隨後將其設置為spring boot ApplicationContext的父級。為了更好的演示向上回溯查找Bean的效果,特意定義了一個MyService Bean,這個Bean注入到spring boot ApplicationContext中,並且在MyService Bean中自動注入對應用所定義的Environment Bean的依賴。當應用程式啟動之後,沒有報錯。說明了MyService Bean成功注入了父級的application context中所定義的Environment Bean
1 import org.springframework.boot.SpringApplication; 2 import org.springframework.context.ConfigurableApplicationContext; 3 import org.springframework.context.support.StaticApplicationContext; 4 5 public class MySpringApplication extends SpringApplication { 6 7 public MySpringApplication(Class<?>[] classes){ 8 super(classes); 9 } 10 11 @Override 12 protected ConfigurableApplicationContext createApplicationContext(){ 13 14 StaticApplicationContext parent=new StaticApplicationContext(); 15 parent.registerBean(Environment.class); 16 parent.refresh(); 17 18 ConfigurableApplicationContext child=super.createApplicationContext(); 19 child.setParent(parent); 20 21 return child; 22 } 23 }
結尾
寫這篇文章的旨在分享有關spring ApplicationContext 一個小小的知識點,Spring所涉及到的知識點非常龐雜。那篇文章的部落客為了弄清楚問題的真相,花費大量的程式來調試程式,還花了大篇幅的文章記錄下來,可以看到出該部落客是一個對技術有著執著追求的人。這篇文章也完整呈現了我在那篇文章評論區中所提到,可以採用對applicationcontext進行分層來解決這一問題。