Joern Barthel opened SPR-7560 and commented
How to reproduce
Annotate a void public method with @Async
and @PostConstruct
. Setup an application context with the appropriate task:executor infrastructure to make @Async
work.
Excepted behavior
@PostConstruct
method should a) be executed in a different thread and b) not block the initialization of the application context.
Actual behaviour
@PostConstruct
method is executed in main Thread and blocks. After method returns Spring bails with an exception:
NoSuchBeanDefinitionException: No matching bean of type [<omitted>] found for dependency: expected at least 1 bean which qualifies as autowire candidate for this dependency. Dependency annotations: {}
Workaround
Only known workaround atm. is to manually execute the logic in the @PostConstruct
method in a Thread
.
Motivation
The application has quite a few state transfers which need to run initially and can easily be parallelized. Business logics accesses the result of those state transfers not necessarily immediately after initialization. Access to asynchronously generated values is easy using patterns such as Google Guava's ValueFuture<T>
.
Affects: 3.0 GA
Issue Links:
- #12218 Document limitations of @Async
annotation
3 votes, 8 watchers
Comment From: spring-projects-issues
Chris Beams commented
Hi Joern,
An interesting use case, and something to consider, but it's not quite a bug. We should certainly document the limitations of @Async
(See #12218), but it was never designed with @PostConstruct
in mind. Right now it's an ordering issue - initialization callbacks are invoked before any proxying (e.g. for async) are created.
We can consider this as a possibility for 3.1.
Comment From: spring-projects-issues
Joern Barthel commented
Agreed. Should we add a sub-task "issue warning when both annotations are present" as an intermediate?
Comment From: spring-projects-issues
Chris Beams commented
I'd say let's wait on that until it's clear whether we'll implement this for 3.1 (or at all). If not, a warning would be appropriate. If yes, it's probably wasted effort right now.
Comment From: spring-projects-issues
Oliver Drotbohm commented
I wonder whether the expected behavior is actually consistent to the contract of the general Spring bean lifecycle. Invoking an @PostConstruct
callback asynchronously will probably lead to the situation, that a bean is still initializing although it is considered completely initialized by the container after the bean leaves the post-processing phase. So this might lead to situations where one can call ApplicationContext.getBean(…)
getting a reference to a bean that is not completely initialized.
So IMHO a scenario like the one you describe actually aims to execute behavior after the bean is completely initialized. That's why I'd argue that @PostConstruct
is not the right tool to achieve this. I'd rather recommend to implement some initializer bean that holds a reference to the bean to initialize and then invokes the method annotated with @Async
. A similar pattern is used in the DataSource
initializing package of Spring JDBC (see DataSourceInitializer
).
Another way might be to implement an ApplicationEventListener
and trigger the asynchronous method when the context is completely loaded (a typical hook to implement "right after the app has started" logic).
Comment From: spring-projects-issues
Oliver Drotbohm commented
Added some wiki syntax where appropriate.
Comment From: spring-projects-issues
Joern Barthel commented
I'd argue that having 1..n async initializers does not add any inconsistencies to the bean lifecycle. After the @PostConstruct
initializers are called the bean should always be "ready to use" (as a dependency) - this behavior is not different. If the functionality of the bean is impaired (e.g. calls to some methods block a thread) or e.g. degraded before some @Async
callback completes is a completely transparent affair to the developer.
The standard use case for me is currently: having a bunch of immutable Map
, List
etc. decorators which are asynchronously populated by DAOs. The application uses these collections a lot but not immediately after startup. Marking the init methods in the decorators as @Async
and @PostConstruct
is very simple & straightforward. Fetching the actual delegate
from a ValueFuture
is also very easy and straightforward.
Comment From: spring-projects-issues
Juergen Hoeller commented
Closing groups of outdated issues. Please reopen if still relevant.
Comment From: spring-projects-issues
heanssgen-troy commented
The use case of calling a method annotated by both @Async
and @PostConstruct
becomes a lot more desirable when working with things like the spring-webflux
framework. Here, it is entire acceptable for a cached set of data (JDBC/Spring Cloud) to be returned using the Mono/Flux
paradigm, which means caching a future in an object is viable. EG.
public class NodeManager {
@Autowired
private ExecutorService executorService;
private CompletableFuture<Map<String, String>> nodeSet;
@Async
@PostConstruct
public Future<Map<String, String>> initNodeSets() {
this.nodeSet = CompletableFuture.supplyAsync(() -> {
return getNodeSet();
}, executorService);
}
public CompletableFuture<Map<String, String>> get() {
return this.nodeSet;
}
}
@RestController
public class NodeController {
@Autowired
private NodeManager nodeManager;
@RequestMapping("/nodes")
public Mono<Map<String, String>> getNodes() {
return Mono.fromFuture(nodeManager.get());
}
}
In this case, we completely understand the implications of having a @PostConstruct
annotation end up with the bean not fully initialized, however it is at the point where we can use it in a pure asynchronous manner. The concurrency here does not matter to us (easily solved using locks if needed), but the performance and readability of the code is drastically improved compared to manually using threads. The Mono
object will only return once the future is complete without any additional handling.
Comment From: spring-projects-issues
heanssgen-troy commented
Reopened for discussion since frameworks have evolved around us.
Comment From: spring-projects-issues
Bulk closing outdated, unresolved issues. Please, reopen if still relevant.
Comment From: inad9300
Could someone confirm whether this feature has been added or not? It is unclear why was the issue closed.
Comment From: sbrannen
As stated in https://github.com/spring-projects/spring-framework/issues/12217#issuecomment-453715845, this issue was bulk closed as "outdated" and "unresolved", which means the requested feature was not implemented.
Comment From: inad9300
It felt like an automatic comment, that's why I wasn't sure. Also, I thought it wasn't so unlikely that something had been done about this problem but wasn't linked to this GitHub issue, given that it was raised almost 10 years ago.
Comment From: baltiyskiy
Can we please reopen it? @heanssgen-troy is right - there are cases where you want to load some data and consider bean constructed only when it's done., and after that use only synchronous API. Otherwise, asynchronicity "creeps" out into all the code that works with that bean, which is very intrusive.
Comment From: snicoll
Otherwise, asynchronicity "creeps" out into all the code that works with that bean, which is very intrusive.
It doesn't have to be. You can have a separate component that calls this API from the same package for startup purposes or inject an AsyncTaskExecutor
to execute your task asynchronously.
Comment From: baltiyskiy
After some consideration, I don't agree with @heanssgen-troy. In the example posted, you could simply replace return type of @PostConstruct
method to void
and everything would work. I think that @Async @PostConstruct
should really postpone the creation of all dependent beans until the future is completed.
@snicoll There should be a sync-async barrier somewhere anyway. And I think this is the case where Spring would be extremely useful by essentially hiding this barrier from the application code.
In our case, we have an important component (feature flags!) that reads these flags on startup. We want logic that depends on this flag to work only when the flags are loaded, in a synchronous and non-blocking manner. Almost all our code is in non-blocking context; sometimes it is asynchronous (Kotlin suspend
functions or CompletionStage
), but sometimes not, and I don't want the latter code to be forced to be asynchronous just because it needs to read a feature flag.
Also, we don't really want to block inside @PostConstruct
because it will postpone the initialization of other beans that do not depend on this bean, and also because sometimes we might be doing this in a non-blocking context (inside a ForkJoinPool).
So, it comes down to this: - Initialization should be asynchronous and non-blocking, - But we want to be able to use the results of the initialization in non-blocking, synchronous manner.
So this might lead to situations where one can call ApplicationContext.getBean(…) getting a reference to a bean that is not completely initialized.
What happens when @PostConstruct
blocks for a long period of time and one uses getBean()
from another thread? I think the behaviour should be the same in this case too until the future is completed.