Tomasz Nurkiewicz opened SPR-8609 and commented

In the following @Configuration class assertion fails because the SpEL expression is not evaluated prior to running bar():

@Configuration
public class ContextConfiguration {

    @Resource
    private Foo foo;

    @Value("#{2+3}")
    private int value;

    @Bean
    public Bar bar() {
        Assert.isTrue(value == 5, Integer.toString(value));
        return new Bar();
    }

}

Because cyclic dependency is introduced, bar() is executed by the container on ContextConfiguration that is not yet populated:

@Service
public class Foo {

    @Resource
    private Bar bar;

}

public class Bar {
}

I am aware that fixing this might be a bit challenging, but at least an exception should be thrown rather than silently calling @Bean method on uninitialized @Configuration class. Full failing test case is available here (spring-cyclic-dep-bug branch).


Affects: 3.1 M2

Issue Links: - #13226 unresolvable circular reference when bean defined in xml config refers to bean defined in outer java config

Comment From: spring-projects-issues

Chris Beams commented

Tomasz,

This certainly makes sense, however the implementation is not terribly trivial. The only way to track this sort of thing would be via a ThreadLocal registry of bean method invocations interacting with the enhanced CGLIB subclass @Bean methods. It's a fair bit of overhead both at actual runtime and in general implementation complexity to achieve better error reporting for what is probably a relatively infrequent mistake.

For this reason I'm moving this to the General Backlog and marking as 'waiting-for-demand'; we'll certainly pay attention if other users weigh in here with additional comments, votes, etc.

In any case, thanks for the submission.

Comment From: spring-projects-issues

Janning Vygen commented

We had a very similar problem with circular references which was really annoying and in my opinion is a bug. I put it here as I think this relates to this issue.

@Configuration
public class ContextConfiguration {

    @Resource
    private Foo foo;

    @Bean
    public Bar bar() {
        Bar bar = new Bar();
                bar.setFoo(foo);
    }
}

@Service
public class Foo {

    @Resource
    private Bar bar;

}

public class Bar {
}

this has worked fine on our development machines, but NOT on our production machines as the "bar" bean was operating with a null value of foo. So we got a NPE.

I guess that component scanning worked differently on my development machine (ubuntu/jetty) than on my production machine (debian/tomcat).

We have four operations which can run like this: - Foo.newInstance() - ContextConfiguration.inject(foo) - bar() - Foo.inject(bar)

But on our production machine spring did in a different way so we ended up with NPE because foo was null at the time bar() was called.

This should be fixed as this is not deterministic.

Comment From: bdshadow

This issue can be closed. When the example above is run, exception is thrown as expected:

2023-06-16 15:30:11.392 WARN 45054 --- [ main] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contextConfiguration': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'foo': Injection of resource dependencies failed; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'contextConfiguration': Requested bean is currently in creation: Is there an unresolvable circular reference?

Description: The dependencies of some of the beans in the application context form a cycle: ┌─────┐ | contextConfiguration ↑ ↓ | foo └─────┘ Action: Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.

Comment From: snicoll

Yes, I agree that the failure analyzer in Spring Boot is providing a solution for this.