简体   繁体   English

Spring:循环依赖,@PostConstruct 和@DependsOn 强加的顺序

[英]Spring: circular dependencies, @PostConstruct and order imposed by @DependsOn

I was expecting Spring to take @DependsOn into account when calling @PostConstruct methods, but seems like it's not the case in presence of circular (auto-wired) dependencies.我希望 Spring 在调用@PostConstruct方法时考虑@DependsOn ,但似乎在存在循环(自动连接)依赖项的情况下并非如此。

Consider two beans (code below), BeanB @DependsOn BeanA .考虑两个 bean(下面的代码), BeanB @DependsOn BeanA When field BeanA#b has it's @Autowired commented out, post-construct methods are called in expected order: first A, then B. But with @Autowired in effect for A, I have B's post called first, then A's post .当字段BeanA#b将其@Autowired注释掉时,按预期顺序调用后构造方法:首先是 A,然后是 B。但是由于@Autowired对 A 有效,我先调用 B 的post然后调用 A 的post

I understand this is a bad design (actually, it's minimal demo of very big @Autowired ... code-base), but I was expecting Spring to finish injection of @Autowired fields and then starting to call lifecycle callbacks, honoring @DependsOn , but Spring seems to ignore @DependsOn order when there are circular deps.我知道这是一个糟糕的设计(实际上,它是非常大的@Autowired ... 代码库的最小演示),但我期待 Spring 完成@Autowired字段的注入,然后开始调用生命周期回调,尊重@DependsOn ,但是当有循环 deps 时,Spring 似乎忽略了@DependsOn顺序。

Spring version is 4.1.5. Spring 版本是 4.1.5。

So, is this my misunderstanding or undocumented behavior or can it be considered a Spring bug (or, perhaps, feature request)?那么,这是我的误解或未记录的行为,还是可以将其视为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()) +
                '}';
    }
}

In the chapter about Initialization callbacks , the Spring documentation states在关于初始化回调的章节中,Spring 文档指出

[ @PostConstruct and other methods] allows a bean to perform initialization work after all necessary properties on the bean have been set by the container. [ @PostConstruct和其他方法] 允许在容器设置 bean 的所有必要属性后,bean 执行初始化工作。

With your commented code, the following happens: beanA is instantiated and saved.使用您的注释代码,会发生以下情况: beanA被实例化并保存。 The container sees that all necessary properties have been set and it invokes the init ( @PostConstruct ) method.容器看到所有必需的属性都已设置并调用 init ( @PostConstruct ) 方法。 Then it goes to beanB which it initializes, saves, sees an @Autowired , retrieves the saved beanA , injects it, the runs beanB 's @PostConstruct since all its properties have been set.然后它转到beanB ,它初始化、保存、查看@Autowired 、检索保存的beanA 、注入它,运行beanB@PostConstruct因为它的所有属性都已设置。

In your uncommented code, you have a case of circular dependencies.在您未注释的代码中,您有一个循环依赖的情况。 beanA gets instantiated first and is saved. beanA被实例化并被保存。 The container notices it has an injection target of type BeanB .容器注意到它有一个BeanB类型的注入目标。 To perform this injection, it needs the beanB bean.要执行此注入,它需要beanB bean。 It therefore instantiates the bean, saves it, sees that it has a dependency on a beanA as an injection target.因此它实例化 bean,保存它,看到它依赖beanA作为注入目标。 It retrieves the beanA (which was saved earlier), injects it, then beanB 's properties are all set and its @PostConstruct method is invoked.它检索beanA (之前保存的),注入它,然后beanB的属性全部设置并调用它的@PostConstruct方法。 Finally, this initialized beanB bean is injected into beanA , whose @PostConstruct method is then invoked since all its properties have been set.最后,这个初始化的beanB bean 被注入到beanA ,然后调用它的@PostConstruct方法,因为它的所有属性都已设置。

This second has case beanB being constructed while beanA is being constructed.第二个案例beanB正在构建,而beanA正在构建。 This is how Spring solves the following这就是Spring如何解决以下问题

class A {
    private B b;
}

class B {
    private A a;
}

An instance of each has to be created before either can be injected into the other.必须先创建每个实例,然后才能将其注入另一个。


If you get rid of the @DependsOn , you'll get the same behavior (but just because of the default ordering of classpath scanning, which seems to be alphabetical).如果你去掉@DependsOn ,你会得到相同的行为(但仅仅是因为类路径扫描的默认顺序,这似乎是按字母顺序排列的)。 If you renamed BeanA to BeanZ , for example, the beanB will be instantiated first, then beanZ would get instantiated, initialized, and returned to be injected into beanB .如果你改名BeanABeanZ ,例如beanB将首先实例化,然后beanZ会得到实例化,初始化,并返回到注入beanB

@DependsOn is really only necessary if you have side effects that you'd like to happen before a bean is initialized. @DependsOn只有在您希望在 bean 初始化之前发生的副作用时才真正需要。

Inspired by Sotirios' answer, and with some investigation:受到 Sotirios 回答的启发,并进行了一些调查:

class A {
    private B b;
}

class B {
    private A a;
}

What spring will do is instantiate an a and b , spring 会做的是实例化ab

Then it does "setup phase" for a .然后它为a . It sets ab=b;它设置ab=b; Then it will call @PostConstruct method on a .然后,它会调用@PostConstruct的方法a

Then it does "setup" for b.然后它为 b 做“设置”。 It sets ba=a;它设置ba=a; and then calls @PostConstruct method on b.然后在 b 上调用@PostConstruct方法。

So if you look carefully, at a 's @PostConstruct time, b hasn't been..fully set up yet.所以如果你仔细看,在a@PostConstruct时间, b还没有......完全设置好。 Like... @AutoWired 's haven't even been assigned yet.就像... @AutoWired甚至还没有被分配。

ba is null during a 's @PostConstruct . baa@PostConstruct期间为空。

Apparently this is "the spring way"?显然这是“春天的方式”?

So if a 's @PostConstruct calls some method on b b.tell_me_about_a , b's circular a will not be assigned yet, so the end result (if tell_me_about_a calls a method of a ) is a callstack that's like所以,如果a@PostConstruct调用b上的一些方法b.tell_me_about_a ,B的圆a不会被尚未分配,所以最终的结果(如果tell_me_about_a调用的方法a )是调用栈这就像

NullPointerException # calling some A method
    someLineOfB
    somePostConstructMethodOfA

However, during b 's @PostConstruct ba will have the instance already fully initiated, and is not lacking its @AutoWired 's, so bab will not be a NullPointerException.但是,在b@PostConstruct期间ba将有实例已经完全启动,并且缺少它的@AutoWired ,因此bab不会是 NullPointerException。 Confusing...令人困惑...

@DependsOn can change some ordering. @DependsOn可以更改一些顺序。 But it seems that it's in the opposite order to what you'd expect .但它似乎与您期望的顺序相反。 I think you are right when it is circular you can't depend on @DependsOn [?] Because it works as expected (runs PostConstruct's in the expected order if there isn't. To me this feels like a bug.我认为当它是循环的时,你是对的,你不能依赖 @DependsOn [?] 因为它按预期工作(如果没有,则按预期顺序运行 PostConstruct。对我来说,这感觉像是一个错误。

You can see this behavior in action by adding spring logging , then you'll see messages like 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您可以通过添加 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类的消息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演示

Ways you can fix it:您可以修复它的方法:

Be careful using methods of injected dependencies during PostConstruct.在 PostConstruct 期间小心使用注入依赖项的方法。 Or call none.或者不叫。

Using spring-wired constructor parameters instead of @AutoWired/@Inject at all.完全使用弹簧接线构造函数参数而不是@AutoWired/@Inject Spring disallows circular in that case.在这种情况下,Spring 不允许使用循环。 Which may be what you want anyway...无论如何,这可能是你想要的......

Use @DependsOn in reverse order?以相反的顺序使用@DependsOn? what?...什么?...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM