亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解Spring 中如何控制2個(gè)bean中的初始化順序

 更新時(shí)間:2017年10月16日 09:33:59   作者:Chown  
本篇文章主要介紹了Spring 中如何控制2個(gè)bean中的初始化順序,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下

開發(fā)過程中有這樣一個(gè)場景,2個(gè) bean 初始化邏輯中有依賴關(guān)系,需要控制二者的初始化順序。實(shí)現(xiàn)方式可以有多種,本文結(jié)合目前對(duì) Spring 的理解,嘗試列出幾種思路。

場景

假設(shè)A,B兩個(gè) bean 都需要在初始化的時(shí)候從本地磁盤讀取文件,其中B加載的文件,依賴A中加載的全局配置文件中配置的路徑,所以需要A先于B初始化,此外A中的配置改變后也需要觸發(fā)B的重新加載邏輯,所以A,B需要注入彼此。

對(duì)于下面的模型,問題簡化為:我們需要initA()先于initB()得到執(zhí)行。

@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

我們可以在業(yè)務(wù)層自己控制A,B的初始化順序,在A中設(shè)置一個(gè)“是否初始化的”標(biāo)記,B初始化前檢測A是否得以初始化,如果沒有則調(diào)用A的初始化方法,所謂的check-and-act。對(duì)于上述模型,實(shí)現(xiàn)如下:

@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");
  }

執(zhí)行效果:

A construct
B construct
A init
B init

這種立flag的方法好處是可以做到lazy initialization,但是如果類似邏輯很多的話代碼中到處充斥著類似代碼,不優(yōu)雅,所以考慮是否框架本身就可以滿足我們的需要。

方案二:使用DependsOn

Spring 中的 DependsOn 注解可以保證被依賴的bean先于當(dāng)前bean被容器創(chuàng)建,但是如果不理解Spring中bean加載過程會(huì)對(duì) DependsOn 有誤解,自己也確實(shí)踩過坑。對(duì)于上述模型,如果在B上加上注解@DependsOn({"a"}),得到的執(zhí)行結(jié)果是:

A construct
B construct
B init
A init

在這里問題的關(guān)鍵是:bean屬性的注入是在初始化方法調(diào)用之前。

// 代碼位置:AbstractAutowireCapableBeanFactory.doCreateBean
// 填充 bean 的各個(gè)屬性,包括依賴注入
populateBean(beanName, mbd, instanceWrapper);
if (exposedObject != null) {
  // 調(diào)用初始化方法,如果是 InitializingBean 則先調(diào)用 afterPropertiesSet 然后調(diào)用自定義的init-method 方法
  exposedObject = initializeBean(beanName, exposedObject, mbd);
}

結(jié)合本例,發(fā)生的實(shí)際情況是,因?yàn)槌霈F(xiàn)了循環(huán)依賴,A依賴B,加載B,B依賴A,所以得到了一個(gè)提前暴露的A,然后調(diào)用B的初始化方法,接著回到A的初始化方法。具體源碼分析過程如下:

ApplicationContext 在 refresh 過程中的最后會(huì)加載所有的 no-lazy 單例。

本例中,先加載的bean A,最終通過無參構(gòu)造器構(gòu)造,然后,繼續(xù)屬性填充(populateBean),發(fā)現(xiàn)需要注入 bean B。所以轉(zhuǎn)而加載 bean B(遞歸調(diào)用 getBean())。此時(shí)發(fā)現(xiàn) bean B 需要 DependsOn("a"),在保存依賴關(guān)系(為了防止循環(huán) depends)后,調(diào)用 getBean("a"),此時(shí)會(huì)得到提前暴露的 bean A ,所以繼續(xù) B 的加載,流程為: 初始化策略構(gòu)造實(shí)例 -> 屬性填充(同樣會(huì)注入提前暴露的 bean A ) -> 調(diào)用初始化方法。

// 代碼位置:AbstractBeanFactory.doGetBean
// Guarantee initialization of beans that the current bean depends on. 實(shí)例化依賴的 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 依賴的關(guān)系
        getBean(dep);
      }
    }

得到提前暴露的 bean A的過程為:

此時(shí)此刻,bean A 的屬性注入完成了, 返回到調(diào)用初始化方法,所以表現(xiàn)的行為是:構(gòu)造A -> 構(gòu)造B -> B初始化 -> A初始化。

DependsOn只是保證的被依賴的bean先于當(dāng)前bean被實(shí)例化,被創(chuàng)建,所以如果要采用這種方式實(shí)現(xiàn)bean初始化順序的控制,那么可以把初始化邏輯放在構(gòu)造函數(shù)中,但是復(fù)雜耗時(shí)的邏輯仿造構(gòu)造器中是不合適的,會(huì)影響系統(tǒng)啟動(dòng)速度。

方案三:容器加載bean之前

Spring 框架中很多地方都為我們提供了擴(kuò)展點(diǎn),很好的體現(xiàn)了開閉原則(OCP)。其中 BeanFactoryPostProcessor 可以允許我們在容器加載任何bean之前修改應(yīng)用上下文中的BeanDefinition(從XML配置文件或者配置類中解析得到的bean信息,用于后續(xù)實(shí)例化bean)。

在本例中,就可以把A的初始化邏輯放在一個(gè) BeanFactoryPostProcessor 中。

@Component
public class ABeanFactoryPostProcessor implements BeanFactoryPostProcessor {
  @Override
  public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
    A.initA();
  }
}

執(zhí)行效果:

A init
A construct
B construct
B init

這種方式把A中的初始化邏輯放到了加載bean之前,很適合加載系統(tǒng)全局配置,但是這種方式中初始化邏輯不能依賴bean的狀態(tài)。

方案四:事件監(jiān)聽器的有序性

Spring 中的 Ordered 也是一個(gè)很重要的組件,很多邏輯中都會(huì)判斷對(duì)象是否實(shí)現(xiàn)了 Ordered 接口,如果實(shí)現(xiàn)了就會(huì)先進(jìn)行排序操作。比如在事件發(fā)布的時(shí)候,對(duì)獲取到的 ApplicationListener 會(huì)先進(jìn)行排序。

// 代碼位置: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;
  }

所以可以利用事件監(jiān)聽器在處理事件時(shí)的有序性,在應(yīng)用上下文 refresh 完成后,分別實(shí)現(xiàn)A,B中對(duì)應(yīng)的初始化邏輯。

@Component
public class ApplicationListenerA implements ApplicationListener<ApplicationContextEvent>, Ordered {
  @Override
  public void onApplicationEvent(ApplicationContextEvent event) {
    initA();
  }

  @Override
  public int getOrder() {
    return Ordered.HIGHEST_PRECEDENCE; // 比 ApplicationListenerB 優(yōu)先級(jí)高
  }

  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");
  }
}

執(zhí)行效果:

A construct
B construct
A init
B init

這種方式就是站在事件響應(yīng)的角度,上下文加載完成后,先實(shí)現(xiàn)A邏輯,然后實(shí)現(xiàn)B邏輯。

總結(jié)

在平時(shí)的開發(fā)中使用的可能都是一個(gè)語言,一個(gè)框架的冰山一角,隨著對(duì)語言,對(duì)框架的不斷深入,你會(huì)發(fā)現(xiàn)更多的可能。本文只是基于目前對(duì)于 Spring 框架的理解做出的嘗試,解決一個(gè)問題可能有多種方式,其中必然存在權(quán)衡選擇,取決于對(duì)業(yè)務(wù)對(duì)技術(shù)的理解。

以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 向量數(shù)據(jù)庫之如何使用Elasticsearch實(shí)現(xiàn)向量數(shù)據(jù)存儲(chǔ)與搜索

    向量數(shù)據(jù)庫之如何使用Elasticsearch實(shí)現(xiàn)向量數(shù)據(jù)存儲(chǔ)與搜索

    這篇文章主要介紹了向量數(shù)據(jù)庫之如何使用Elasticsearch實(shí)現(xiàn)向量數(shù)據(jù)存儲(chǔ)與搜索,在向量函數(shù)的計(jì)算過程中,會(huì)對(duì)所有匹配的文檔進(jìn)行線性掃描,因此,查詢預(yù)計(jì)時(shí)間會(huì)隨著匹配文檔的數(shù)量線性增長,本文給大家講解的非常詳細(xì),需要的朋友參考下吧
    2023-06-06
  • SpringCloud如何解決服務(wù)之間的通信問題

    SpringCloud如何解決服務(wù)之間的通信問題

    本文主要介紹了SpringCloud如何解決服務(wù)之間的通信問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-08-08
  • 微信開發(fā)協(xié)議小結(jié)

    微信開發(fā)協(xié)議小結(jié)

    通過本教程給大家分享微信開發(fā)協(xié)議小結(jié)的相關(guān)知識(shí),非常不錯(cuò),具有一定的參考借鑒價(jià)值,感興趣的朋友一起看看吧
    2016-11-11
  • Javabean基于xstream包實(shí)現(xiàn)轉(zhuǎn)XML文檔的方法

    Javabean基于xstream包實(shí)現(xiàn)轉(zhuǎn)XML文檔的方法

    這篇文章主要介紹了Javabean基于xstream包實(shí)現(xiàn)轉(zhuǎn)XML文檔的方法,結(jié)合具體實(shí)例形式分析了xstream包用于轉(zhuǎn)換xml文件的具體使用技巧,需要的朋友可以參考下
    2017-05-05
  • 兩種Eclipse部署動(dòng)態(tài)web項(xiàng)目方法

    兩種Eclipse部署動(dòng)態(tài)web項(xiàng)目方法

    這篇文章主要介紹了兩種Eclipse部署動(dòng)態(tài)web項(xiàng)目方法,需要的朋友可以參考下
    2015-11-11
  • springboot使用webservice發(fā)布和調(diào)用接口的實(shí)例詳解

    springboot使用webservice發(fā)布和調(diào)用接口的實(shí)例詳解

    本文介紹了如何在Springboot中使用webservice發(fā)布和調(diào)用接口,涵蓋了必要的依賴添加和代碼示例,文中提供了服務(wù)端和客戶端的實(shí)現(xiàn)方法,以及如何設(shè)置端口和服務(wù)地址,幫助讀者更好地理解和應(yīng)用Springboot結(jié)合webservice的技術(shù)
    2024-10-10
  • Java AtomicInteger類使用方法實(shí)例講解

    Java AtomicInteger類使用方法實(shí)例講解

    這篇文章主要介紹了Java AtomicInteger類使用方法實(shí)例講解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • SpringBoot整合Redis實(shí)現(xiàn)登錄失敗鎖定功能(實(shí)例詳解)

    SpringBoot整合Redis實(shí)現(xiàn)登錄失敗鎖定功能(實(shí)例詳解)

    本文我們已經(jīng)探討如何利用Redis來實(shí)現(xiàn)鎖定賬戶的安全措施,以及通過SpringBoot整合Redis實(shí)現(xiàn)了這一功能,感興趣的朋友跟隨小編一起學(xué)習(xí)下吧
    2024-02-02
  • SpringMVC之AbstractAnnotationConfigDispatcherSer解讀

    SpringMVC之AbstractAnnotationConfigDispatcherSer解讀

    這篇文章主要介紹了SpringMVC之AbstractAnnotationConfigDispatcherSer,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05
  • SpringBoot2.0如何自定義404頁面渲染

    SpringBoot2.0如何自定義404頁面渲染

    這篇文章主要介紹了SpringBoot2.0如何自定義404頁面渲染問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-12-12

最新評(píng)論