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.
Comment From: kse-music
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() {
Comment From: BartRobeyns
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 1. 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) 1. 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.
Comment From: kse-music
oops,may be your issue is similar to #25443 ,Thank you for @BartRobeyns interpretion
Comment From: BartRobeyns
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.
Comment From: rstoyanchev
Added to triage queue as there is some change in behavior, possibly requiring a small documentation update/clarification?
Comment From: abysas
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());
}
}
Comment From: sbrannen
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.
Comment From: BartRobeyns
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).
That's why I felt the release note doesn't apply here: it didn't use to be a circular dependency.
Or I'm wrong about the warning? :)
Comment From: sbrannen
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.
Comment From: BartRobeyns
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.
Comment From: snicoll
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.
Comment From: BartRobeyns
@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)
Comment From: snicoll
@BartRobeyns the issue is still open so it means we are still considering documenting something.