【问题标题】:Spring: circular dependencies, @PostConstruct and order imposed by @DependsOnSpring:循环依赖、@PostConstruct 和@DependsOn 强加的顺序
【发布时间】:2015-03-25 16:02:27
【问题描述】:

我希望 Spring 在调用 @PostConstruct 方法时会考虑 @DependsOn,但在存在循环(自动连接)依赖项时似乎并非如此。

考虑两个 bean(下面的代码)BeanB @DependsOn BeanA。当字段BeanA#b 将其@Autowired 注释掉时,按预期顺序调用构造后方法:首先是A,然后是B。但是@Autowired 对A 有效,我首先调用了B 的post然后 A的post

我知道这是一个糟糕的设计(实际上,它是非常大的 @Autowired ... 代码库的最小演示),但我期待 Spring 完成 @Autowired 字段的注入,然后 em> 开始调用生命周期回调,尊重@DependsOn,但是当有循环依赖时,Spring 似乎忽略了@DependsOn 顺序。

Spring 版本是 4.1.5。

那么,这是我的误解未记录的行为还是可以将其视为Spring错误(或者,也许是功能请求)?

@Component
class BeanA {

    // @Autowired
    private BeanB b;

    void f() {
        System.out.println(this);
    }

    @PostConstruct
    void post() {
        System.out.println("A done");
    }

    @Override
    public String toString() {
        return "Bean{" +
                "b=" + (b == null ? null : b.getClass()) +
                '}';
    }
}
// ---------------------
@Component
@DependsOn("beanA")
class BeanB {

    @Autowired
    private BeanA a;

    void f() {
        System.out.println(this);
    }

    @PostConstruct
    void post() {
        System.out.println("B done");
    }

    @Override
    public String toString() {
        return "BeanB{" +
                "a=" + (a == null ? null : a.getClass()) +
                '}';
    }
}

【问题讨论】:

    标签: java spring


    【解决方案1】:

    在关于Initialization callbacks 的章节中,Spring 文档指出

    [@PostConstruct和其他方法]允许一个bean执行 在 bean 上的所有必要属性都具有之后的初始化工作 由容器设置。

    使用您的注释代码,会发生以下情况:beanA 被实例化并保存。容器看到所有必要的属性都已设置并调用 init (@PostConstruct) 方法。然后它转到beanB,它初始化、保存、查看@Autowired、检索保存的beanA、注入它,运行beanB@PostConstruct,因为它的所有属性都已设置。

    在您未注释的代码中,您有一个循环依赖的情况。 beanA 首先被实例化并被保存。容器注意到它有一个BeanB 类型的注入目标。要执行此注入,它需要beanB bean。因此,它实例化 bean,保存它,发现它依赖于 beanA 作为注入目标。它检索beanA(之前保存的),注入它,然后设置beanB 的所有属性并调用它的@PostConstruct 方法。最后,将这个初始化的beanB bean 注入到beanA,然后调用@PostConstruct 方法,因为它的所有属性都已设置。

    第二个案例beanB 正在构建,而beanA 正在构建。这就是 Spring 解决以下问题的方式

    class A {
        private B b;
    }
    
    class B {
        private A a;
    }
    

    必须先创建每个实例的实例,然后才能将其中任何一个注入另一个。


    如果你去掉@DependsOn,你会得到相同的行为(但这只是因为类路径扫描的默认顺序,这似乎是按字母顺序排列的)。例如,如果将BeanA 重命名为BeanZ,则beanB 将首先被实例化,然后beanZ 将被实例化、初始化并返回以注入beanB

    @DependsOn 仅在您希望在初始化 bean 之前发生副作用时才真正需要。

    【讨论】:

    • 完全正确——对于 B 存在副作用依赖(在完整版本中,不在我的示例代码中):在调用 B 自己的 @PostConstruct 之前完成 A 的生命周期回调。我打算使用@DependsOn 来确保这一点,但是——唉!
    • 在实例化 Spring 上下文后,通过从外部代码调用所需逻辑来解决实际代码问题,而不是依赖于 B#postConstruct()
    • 我在帖子中添加了重点,以使实际问题更加明确。
    • @VictorSorokin 我认为这是您的误解。 @DependsOn 保证创建顺序,并且,确实,在您的两个示例中(如果您将 SOP 添加到无参数构造函数),beanA 是首先创建。初始化是另一回事。 Spring 缓存它创建的 bean,以便它可以解决循环依赖关系,例如您的 sn-p (或我的答案)中的依赖关系。打开调试日志以查看它的运行情况。
    • @VictorSorokin 但我想知道在未注释的情况下您想要什么行为。您是否期望创建beanA,然后创建beanB(但未初始化),注入beanA,然后容器返回完成beanB 的初始化?这就是你想要达到的目标吗?
    【解决方案2】:

    受到 Sotirios 回答的启发,并进行了一些调查:

    class A {
        private B b;
    }
    
    class B {
        private A a;
    }
    

    spring会做的是实例化ab

    然后它为a 执行“设置阶段”。它设置a.b=b; 然后它会在a 上调用@PostConstruct 方法。

    然后它为 b 进行“设置”。它设置b.a=a; 然后在b 上调用@PostConstruct 方法。

    所以如果你仔细观察,在a@PostConstruct 时间,b 还没有……完全设置好。比如...@AutoWired 还没有被分配。

    a@PostConstruct 期间b.a 为空。

    这显然是“春之道”?

    所以如果a@PostConstruct在bb.tell_me_about_a上调用了某个方法,b的循环a还不会被赋值,所以最终结果(如果tell_me_about_a调用a的方法)是像这样的调用栈

    NullPointerException # calling some A method
        someLineOfB
        somePostConstructMethodOfA
    

    但是,在b@PostConstruct 期间,b.a 的实例已经完全启动,并且不会缺少它的@AutoWired,所以b.a.b 不会空指针异常。令人困惑...

    @DependsOn 可以改变一些顺序。但它似乎与你expect 的顺序相反。 我认为当它是循环的时候你是对的,你不能依赖@DependsOn [?] 因为它按预期工作(如果没有,则按预期顺序运行 PostConstruct。对我来说,这感觉像是一个错误。

    您可以通过添加 spring logging 来查看此行为,然后您将看到类似 DEBUG main support.DefaultListableBeanFactory:247 - Returning eagerly cached instance of singleton bean 'beanA' that is not fully initialized yet - a consequence of a circular reference demo 的消息

    解决方法:

    在 PostConstruct 期间小心使用注入依赖项的方法。或者不打电话。

    完全使用弹簧连接的构造函数参数而不是@AutoWired/@Inject。在这种情况下,Spring 不允许循环。无论如何,这可能是您想要的...

    以相反的顺序使用@DependsOn?什么?...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-04-24
      • 1970-01-01
      • 2011-03-29
      • 1970-01-01
      • 2016-10-30
      • 2020-05-06
      • 2020-07-15
      • 2017-04-03
      相关资源
      最近更新 更多