Victor Sorokin
Victor Sorokin

Reputation: 12006

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.

Consider two beans (code below), 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.

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.

Spring version is 4.1.5.

So, is this my misunderstanding or undocumented behavior or can it be considered a Spring bug (or, perhaps, feature request)?

@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()) +
                '}';
    }
}

Upvotes: 4

Views: 7519

Answers (2)

rogerdpack
rogerdpack

Reputation: 66761

Inspired by Sotirios' answer, and with some investigation:

class A {
    private B b;
}

class B {
    private A a;
}

What spring will do is instantiate an a and b,

Then it does "setup phase" for a. It sets a.b=b; Then it will call @PostConstruct method on a.

Then it does "setup" for b. It sets b.a=a; and then calls @PostConstruct method on b.

So if you look carefully, at a's @PostConstruct time, b hasn't been..fully set up yet. Like...@AutoWired's haven't even been assigned yet.

b.a is null during a's @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

NullPointerException # calling some A method
    someLineOfB
    somePostConstructMethodOfA

However, during b's @PostConstruct b.a will have the instance already fully initiated, and is not lacking its @AutoWired's, so b.a.b will not be a NullPointerException. Confusing...

@DependsOn can change some ordering. 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.

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

Ways you can fix it:

Be careful using methods of injected dependencies during PostConstruct. Or call none.

Using spring-wired constructor parameters instead of @AutoWired/@Inject at all. Spring disallows circular in that case. Which may be what you want anyway...

Use @DependsOn in reverse order? what?...

Upvotes: 1

Sotirios Delimanolis
Sotirios Delimanolis

Reputation: 279990

In the chapter about Initialization callbacks, the Spring documentation states

[@PostConstruct and other methods] allows a bean to perform initialization work after all necessary properties on the bean have been set by the container.

With your commented code, the following happens: beanA is instantiated and saved. The container sees that all necessary properties have been set and it invokes the init (@PostConstruct) method. 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.

In your uncommented code, you have a case of circular dependencies. beanA gets instantiated first and is saved. The container notices it has an injection target of type BeanB. To perform this injection, it needs the beanB bean. It therefore instantiates the bean, saves it, sees that it has a dependency on a beanA as an injection target. It retrieves the beanA (which was saved earlier), injects it, then beanB's properties are all set and its @PostConstruct method is invoked. Finally, this initialized beanB bean is injected into beanA, whose @PostConstruct method is then invoked since all its properties have been set.

This second has case beanB being constructed while beanA is being constructed. This is how Spring solves the following

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). 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.

@DependsOn is really only necessary if you have side effects that you'd like to happen before a bean is initialized.

Upvotes: 7

Related Questions