Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Referencing a @Bean method in a @Configuration class' @PostConstruct method leads to circular reference #27876

Closed
BartRobeyns opened this issue Jan 2, 2022 · 13 comments
Assignees
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: documentation A documentation task
Milestone

Comments

@BartRobeyns
Copy link

BartRobeyns commented Jan 2, 2022

After upgrading to spring-boot 2.6.x, it is no longer possible to access a bean in the @PostConstruct method of the @Configuration class where it was defined. This did work before (tested with 2.1.x, 2.5.6).

Example code that will result in a BeanCurrentlyInCreationException, printing "Application Failed to start", with "The dependencies of some of the beans in the application context form a cycle:"

@Configuration
public class AppConf {

    @Bean
    public String getTestBean() {
        return "";
    }

    @PostConstruct
    public void init() {
        getTestBean();
    }

}

You can get a full example at https://github.com/BartRobeyns/boot26ConfigPostConstruct (spring-initializer project with only the @Configuration class above added) that fails to build.

Changing the spring-boot version to 2.5.x will allow the application to build and run.

Note that I'm totally fine with this behavior (@PostConstruct in a @Configuration class seems pretty weird to me), but the documentation of @PostConstruct does state that the

The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.
This annotation must be supported on all classes that support dependency injection.

... so it's easy to expect the sample above to work. An explicit mention that you shouldn't access @Bean methods from the @Configuration class itself would clear that up.

@kse-music
Copy link

You may have three ways to solve it:

  1. set spring.main.allow-circular-references=true
  2. use @configuration(proxyBeanMethods = false) instead of @configuration
  3. modify @bean method to public static String getTestBean() {

@BartRobeyns
Copy link
Author

BartRobeyns commented Jan 2, 2022

Thanks, @kse-music, but each of those solutions comes with serious drawbacks:

  1. spring.main.allow-circular-references has too broad a scope; you actually want Spring to notify you about circular references, because they are a code smell that you want to fix
  2. proxyBeanMethods = false will make the call to getTestBean() return a different instance of the Bean (not in the simple example above of course, because Strings are cached)
  3. turning the getTestBean() into a static leads to the same result as proxyBeanMethods=false: two different beans in the spring-context and in the init-method.

A better example to clarify: in spring-boot < 2.6, the below code simply works, and the test underneath it succeeds.
In spring-boot 2.6, the two last workarounds will make the test fail, because they result in different instances - also pointed out by the two different numbers that are printed before failing the assertEquals.

@Configuration
public class AppConf {

    private static Integer counter = 0;
    public static Integer testBeanFromInit;

    @Bean
    public Integer getTestBean() {
        return ++counter;
    }

    @PostConstruct
    public void init() {
        testBeanFromInit = getTestBean();
    }
}
@SpringBootTest
class DemoApplicationTests {
    @Autowired
    Integer testBean;

    @Test
    void contextLoads() {
        System.out.println(AppConf.testBeanFromInit);
        System.out.println(testBean);
        assertEquals("Not the same bean as in the init", testBean, AppConf.testBeanFromInit);
    }
}

I'm not really looking for a workaround: I simply think the limitations of @configuration's @PostConstruct methods, starting with 2.6.0 should be mentioned somewhere. The release notes do mention "circular references prohibited by default", and that seems to be exactly what's happening: getTestBean() needs AppConf to be fully constructed, while inside the @PostConstruct-method it is still in the process of being constructed. But: it's called PostConstruct, so construction should already be finished, and I should be able to get the testBean. Hence the confusion, and the need to clearly specify the expected behaviour.

@kse-music
Copy link

oops,may be your issue is similar to #25443 ,Thank you for @BartRobeyns interpretion

@snicoll snicoll transferred this issue from spring-projects/spring-boot Jan 3, 2022
@snicoll snicoll changed the title Using a bean defined in a @Configuration bean in its @PostConstruct leads to circular reference in 2.6.x Using a bean defined in a @Configuration bean in its @PostConstruct leads to circular reference Jan 3, 2022
@sbrannen sbrannen changed the title Using a bean defined in a @Configuration bean in its @PostConstruct leads to circular reference Referencing a @Bean method in a @Configuration class' @PostConstruct method leads to circular reference Jan 3, 2022
@sbrannen sbrannen added in: core Issues in core modules (aop, beans, core, context, expression) status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 3, 2022
@BartRobeyns
Copy link
Author

It's not really the same as #25443: that one is about two beans depending on each other - a true circular dependency, and if I read the ticket correct, it was actually failing in 2.5.
In our case the circular dependency is only reported in 2.6, and as the @configuration class referring to itself.

@rstoyanchev rstoyanchev added status: waiting-for-triage An issue we've not yet triaged or decided on and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 4, 2022
@rstoyanchev rstoyanchev added this to the Triage Queue milestone Jan 4, 2022
@rstoyanchev
Copy link
Contributor

Added to triage queue as there is some change in behavior, possibly requiring a small documentation update/clarification?

@jhoeller jhoeller added type: documentation A documentation task and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 18, 2022
@jhoeller jhoeller modified the milestones: Triage Queue, 5.3.16 Jan 18, 2022
@abysas
Copy link

abysas commented Jan 19, 2022

Note that the issue also occurs if post-construct is accessing auto-wired field injected with @Bean created within the same configuration class:

@Configuration
public class AppConf {

    @Autowired
    WebApp webApp;

    @PostConstruct
    public void init() {
        webApp.startApp();
    }

    @Bean
    public WebApp getWebApp() {
        return new WebApp();
    }
}

public class WebApp {
    private boolean started;

    public void startApp() {
        // do start stuff
        System.out.println("Running startApp");
        started = true;
    }

    public boolean isStarted() {
        return started;
    }
}

@SpringBootTest(classes = AppConf.class)
class DemoApplicationTest {
    @Autowired
    WebApp webApp;

    @Test
    void contextLoads() {
        Assertions.assertTrue(webApp.isStarted());
    }
}

@sbrannen
Copy link
Member

Added to triage queue as there is some change in behavior, possibly requiring a small documentation update/clarification?

The change in behavior is in Spring Boot: https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.6-Release-Notes#circular-references-prohibited-by-default.

Though we can certainly document that accessing local @Bean methods or locally injected beans in a @PostConstruct method in a @Configuration is discouraged.

@BartRobeyns
Copy link
Author

BartRobeyns commented Jan 19, 2022 via email

@sbrannen
Copy link
Member

Hi @BartRobeyns,

It could well be that my interpretation is incorrect, and since you feel certain it is a change in behavior in Spring Framework (and not in Spring Boot) we will certainly want to confirm the previous behavior compared to the current behavior in order to decide what documentation to change/improve.

@BartRobeyns
Copy link
Author

Thanks for considering this Sam. I didn't state anything about the source of the change (whether it's Spring Boot or Spring Framework) - and don't really have a clue about it either. I'm only looking at it from the Spring Boot perspective, and noticed the change in behaviour between Spring Boot 2.5 and 2.6.

@snicoll
Copy link
Member

snicoll commented Jan 20, 2022

Note that in Spring Boot this specific case was not reported as a circular reference (normally you'd get a warning in the logs, I believe).

Spring Boot does not log a warning in case of a circular reference that the context can handle. It does provide a failure analysis if the context can't handle it with additional details. That doesn't apply to the case you've described.

That's why I felt the release note doesn't apply here

They do IMO. This circular reference, like others, could be solved transparently by the context (so you'd not know about it) and we made it apparent in Spring Boot 2.6.x by explicitly preventing them by default.

@BartRobeyns
Copy link
Author

BartRobeyns commented Feb 9, 2022

@snicoll that clarifies the release notes for me, thanks.
I still think that an extra warning in the documentation that you shouldn't access @bean methods from the @configuration class itself would help users that are puzzled by the circular reference error mentioning only the @configuration class; it's difficult for a user to understand how that @configuration class can have an invalid circular reference to itself.
(But feel free to close this ticket without further action if you disagree)

@snicoll
Copy link
Member

snicoll commented Feb 9, 2022

@BartRobeyns the issue is still open so it means we are still considering documenting something.

erikpetzold added a commit to erikpetzold/cxf-spring-boot-starter that referenced this issue Nov 21, 2022
jonashackt pushed a commit to codecentric/cxf-spring-boot-starter that referenced this issue Nov 22, 2022
@jhoeller jhoeller modified the milestones: 5.3.x, 6.0.x Feb 1, 2023
@sbrannen sbrannen modified the milestones: 6.0.x, 6.1.x Jul 4, 2023
@jhoeller jhoeller modified the milestones: 6.1.x, 6.0.13 Sep 15, 2023
@jhoeller jhoeller self-assigned this Sep 15, 2023
@jhoeller jhoeller added the for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x label Sep 29, 2023
@github-actions github-actions bot removed the for: backport-to-5.3.x Marks an issue as a candidate for backport to 5.3.x label Sep 29, 2023
@jhoeller jhoeller added the status: backported An issue that has been backported to maintenance branches label Sep 29, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: backported An issue that has been backported to maintenance branches type: documentation A documentation task
Projects
None yet
Development

No branches or pull requests

7 participants