您好,登錄后才能下訂單哦!
開發過程中有這樣一個場景,2個 bean 初始化邏輯中有依賴關系,需要控制二者的初始化順序。實現方式可以有多種,本文結合目前對 Spring 的理解,嘗試列出幾種思路。
場景
假設A,B兩個 bean 都需要在初始化的時候從本地磁盤讀取文件,其中B加載的文件,依賴A中加載的全局配置文件中配置的路徑,所以需要A先于B初始化,此外A中的配置改變后也需要觸發B的重新加載邏輯,所以A,B需要注入彼此。
對于下面的模型,問題簡化為:我們需要initA()先于initB()得到執行。
@Service public class A { @Autowired private B b; public A() { System.out.println("A construct"); } @PostConstruct public void init() { initA(); } private void initA() { System.out.println("A init"); } } @Service public class B { @Autowired private A a; public B() { System.out.println("B construct"); } @PostConstruct public void init() { initB(); } private void initB(){ System.out.println("B init"); } }
方案一:立Flag
我們可以在業務層自己控制A,B的初始化順序,在A中設置一個“是否初始化的”標記,B初始化前檢測A是否得以初始化,如果沒有則調用A的初始化方法,所謂的check-and-act。對于上述模型,實現如下:
@Service public class A { private static volatile boolean initialized; @Autowired private B b; public A() { System.out.println("A construct"); } @PostConstruct public void init() { initA(); } public boolean isInitialized() { return initialized; } public void initA() { if (!isInitialized()) { System.out.println("A init"); } initialized = true; } } @Service public class B { @Autowired private A a; public B() { System.out.println("B construct"); } @PostConstruct public void init() { initB(); } private void initB() { if (!a.isInitialized()) { a.initA(); } System.out.println("B init"); }
執行效果:
A construct
B construct
A init
B init
這種立flag的方法好處是可以做到lazy initialization,但是如果類似邏輯很多的話代碼中到處充斥著類似代碼,不優雅,所以考慮是否框架本身就可以滿足我們的需要。
方案二:使用DependsOn
Spring 中的 DependsOn 注解可以保證被依賴的bean先于當前bean被容器創建,但是如果不理解Spring中bean加載過程會對 DependsOn 有誤解,自己也確實踩過坑。對于上述模型,如果在B上加上注解@DependsOn({"a"}),得到的執行結果是:
A construct
B construct
B init
A init
在這里問題的關鍵是:bean屬性的注入是在初始化方法調用之前。
// 代碼位置:AbstractAutowireCapableBeanFactory.doCreateBean // 填充 bean 的各個屬性,包括依賴注入 populateBean(beanName, mbd, instanceWrapper); if (exposedObject != null) { // 調用初始化方法,如果是 InitializingBean 則先調用 afterPropertiesSet 然后調用自定義的init-method 方法 exposedObject = initializeBean(beanName, exposedObject, mbd); }
結合本例,發生的實際情況是,因為出現了循環依賴,A依賴B,加載B,B依賴A,所以得到了一個提前暴露的A,然后調用B的初始化方法,接著回到A的初始化方法。具體源碼分析過程如下:
ApplicationContext 在 refresh 過程中的最后會加載所有的 no-lazy 單例。
本例中,先加載的bean A,最終通過無參構造器構造,然后,繼續屬性填充(populateBean),發現需要注入 bean B。所以轉而加載 bean B(遞歸調用 getBean())。此時發現 bean B 需要 DependsOn("a"),在保存依賴關系(為了防止循環 depends)后,調用 getBean("a"),此時會得到提前暴露的 bean A ,所以繼續 B 的加載,流程為: 初始化策略構造實例 -> 屬性填充(同樣會注入提前暴露的 bean A ) -> 調用初始化方法。
// 代碼位置:AbstractBeanFactory.doGetBean // Guarantee initialization of beans that the current bean depends on. 實例化依賴的 bean String[] dependsOn = mbd.getDependsOn(); if (dependsOn != null) { for (String dep : dependsOn) { if (isDependent(beanName, dep)) { throw new BeanCreationException(mbd.getResourceDescription(), beanName, "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'"); } registerDependentBean(dep, beanName); // 緩存 bean 依賴的關系 getBean(dep); } }
得到提前暴露的 bean A的過程為:
此時此刻,bean A 的屬性注入完成了, 返回到調用初始化方法,所以表現的行為是:構造A -> 構造B -> B初始化 -> A初始化。
DependsOn只是保證的被依賴的bean先于當前bean被實例化,被創建,所以如果要采用這種方式實現bean初始化順序的控制,那么可以把初始化邏輯放在構造函數中,但是復雜耗時的邏輯仿造構造器中是不合適的,會影響系統啟動速度。
方案三:容器加載bean之前
Spring 框架中很多地方都為我們提供了擴展點,很好的體現了開閉原則(OCP)。其中 BeanFactoryPostProcessor 可以允許我們在容器加載任何bean之前修改應用上下文中的BeanDefinition(從XML配置文件或者配置類中解析得到的bean信息,用于后續實例化bean)。
在本例中,就可以把A的初始化邏輯放在一個 BeanFactoryPostProcessor 中。
@Component public class ABeanFactoryPostProcessor implements BeanFactoryPostProcessor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException { A.initA(); } }
執行效果:
A init
A construct
B construct
B init
這種方式把A中的初始化邏輯放到了加載bean之前,很適合加載系統全局配置,但是這種方式中初始化邏輯不能依賴bean的狀態。
方案四:事件監聽器的有序性
Spring 中的 Ordered 也是一個很重要的組件,很多邏輯中都會判斷對象是否實現了 Ordered 接口,如果實現了就會先進行排序操作。比如在事件發布的時候,對獲取到的 ApplicationListener 會先進行排序。
// 代碼位置:AbstractApplicationEventMulticaster.ListenerRetriever.getApplicationListeners() public Collection<ApplicationListener<?>> getApplicationListeners() { LinkedList<ApplicationListener<?>> allListeners = new LinkedList<ApplicationListener<?>>(); for (ApplicationListener<?> listener : this.applicationListeners) { allListeners.add(listener); } if (!this.applicationListenerBeans.isEmpty()) { BeanFactory beanFactory = getBeanFactory(); for (String listenerBeanName : this.applicationListenerBeans) { try { ApplicationListener<?> listener = beanFactory.getBean(listenerBeanName, ApplicationListener.class); if (this.preFiltered || !allListeners.contains(listener)) { allListeners.add(listener); } } catch (NoSuchBeanDefinitionException ex) { // Singleton listener instance (without backing bean definition) disappeared - // probably in the middle of the destruction phase } } } AnnotationAwareOrderComparator.sort(allListeners); // 排序 return allListeners; }
所以可以利用事件監聽器在處理事件時的有序性,在應用上下文 refresh 完成后,分別實現A,B中對應的初始化邏輯。
@Component public class ApplicationListenerA implements ApplicationListener<ApplicationContextEvent>, Ordered { @Override public void onApplicationEvent(ApplicationContextEvent event) { initA(); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE; // 比 ApplicationListenerB 優先級高 } public static void initA() { System.out.println("A init"); } } @Component public class ApplicationListenerB implements ApplicationListener<ApplicationContextEvent>, Ordered{ @Override public void onApplicationEvent(ApplicationContextEvent event) { initB(); } @Override public int getOrder() { return Ordered.HIGHEST_PRECEDENCE -1; } private void initB() { System.out.println("B init"); } }
執行效果:
A construct
B construct
A init
B init
這種方式就是站在事件響應的角度,上下文加載完成后,先實現A邏輯,然后實現B邏輯。
總結
在平時的開發中使用的可能都是一個語言,一個框架的冰山一角,隨著對語言,對框架的不斷深入,你會發現更多的可能。本文只是基于目前對于 Spring 框架的理解做出的嘗試,解決一個問題可能有多種方式,其中必然存在權衡選擇,取決于對業務對技術的理解。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。