详解Spring 中如何控制2个bean中的初始化顺序

时间:2021-10-29 06:56:29

开发过程中有这样一个场景,2个 bean 初始化逻辑中有依赖关系,需要控制二者的初始化顺序。实现方式可以有多种,本文结合目前对 spring 的理解,尝试列出几种思路。

场景

假设a,b两个 bean 都需要在初始化的时候从本地磁盘读取文件,其中b加载的文件,依赖a中加载的全局配置文件中配置的路径,所以需要a先于b初始化,此外a中的配置改变后也需要触发b的重新加载逻辑,所以a,b需要注入彼此。

对于下面的模型,问题简化为:我们需要inita()先于initb()得到执行。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@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。对于上述模型,实现如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
@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属性的注入是在初始化方法调用之前。

?
1
2
3
4
5
6
7
// 代码位置: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 单例。

详解Spring 中如何控制2个bean中的初始化顺序

本例中,先加载的bean a,最终通过无参构造器构造,然后,继续属性填充(populatebean),发现需要注入 bean b。所以转而加载 bean b(递归调用 getbean())。此时发现 bean b 需要 dependson("a"),在保存依赖关系(为了防止循环 depends)后,调用 getbean("a"),此时会得到提前暴露的 bean a ,所以继续 b 的加载,流程为: 初始化策略构造实例 -> 属性填充(同样会注入提前暴露的 bean a ) -> 调用初始化方法。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 代码位置: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的过程为:

详解Spring 中如何控制2个bean中的初始化顺序

此时此刻,bean a 的属性注入完成了, 返回到调用初始化方法,所以表现的行为是:构造a -> 构造b -> b初始化 -> a初始化。

dependson只是保证的被依赖的bean先于当前bean被实例化,被创建,所以如果要采用这种方式实现bean初始化顺序的控制,那么可以把初始化逻辑放在构造函数中,但是复杂耗时的逻辑仿造构造器中是不合适的,会影响系统启动速度。

方案三:容器加载bean之前

spring 框架中很多地方都为我们提供了扩展点,很好的体现了开闭原则(ocp)。其中 beanfactorypostprocessor 可以允许我们在容器加载任何bean之前修改应用上下文中的beandefinition(从xml配置文件或者配置类中解析得到的bean信息,用于后续实例化bean)。

在本例中,就可以把a的初始化逻辑放在一个 beanfactorypostprocessor 中。

?
1
2
3
4
5
6
7
@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 会先进行排序。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 代码位置: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中对应的初始化逻辑。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@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 框架的理解做出的尝试,解决一个问题可能有多种方式,其中必然存在权衡选择,取决于对业务对技术的理解。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。

原文链接:https://zhuanlan.zhihu.com/p/30112785?utm_source=tuicool&utm_medium=referral