I have an instance of a Spring ApplicationContext in a method and I need to "probe" it and decide if:
- it has been fully
refresh()ed at least once so I can be sure that the beanFactory is done creating all of its beans and - no subsequent
refresh()is currently in progress
Important restriction: In my use case I cannot use a listener for ContextRefreshedEvent. I can only operate on the applicationContext that was passed to me, which might have happened during a refresh(), before or after it.
If you take a look at org.springframework.context.support.AbstractApplicationContext#refresh you will see the following steps, among others:
public void refresh() throws BeansException, IllegalStateException {
// :
prepareRefresh(); // makes this.isActive() return true
// :
finishBeanFactoryInitialization(beanFactory);
// internally calls:
// beanFactory.freezeConfiguration() -> makes beanFactoroy.isConfigurationFrozen() return true
// beanFactory.preInstantiateSingletons()
// :
finishRefresh();
}
The above selection of steps highlights the fact that ConfigurableApplicationContext.isActive() and ConfigurableListableBeanFactory.isConfigurationFrozen() which I have tried do not guarantee that beanFactory.preInstantiateSingletons() has been completed, which is very important in my case.
Ideally I would expect
- A
ConfigurableApplicationContext.isRefreshed()method which would indicate if a context has been fully refreshed at least once and - A
ConfigurableApplicationContext.isBeingRefreshed()which would indicate whether a refresh, the first or a subsequent one, is currently in progress.
Schematically they would fit in the existing refresh() method as follows:
public void refresh() throws BeansException, IllegalStateException {
// Addition 1
setIsBeingRefreshed(true)
// :
prepareRefresh();
// :
finishBeanFactoryInitialization(beanFactory);
// Addition 2
setIsBeingRefreshed(false)
setIsRefreshed(true)
// :
finishRefresh();
}
With that available I would be able to test if isRefreshed() && !isBeingRefreshed() to make sure I have a "stable" and refreshed context.
But no such methods exist so I was wondering if you are aware of anything effectively equivalent.
2 Answers 2
Disclaimer: this answer is picked up from source code inspection, and may not work for different versions of Spring Framework. Code pointers: AbstractApplicationContext#refresh, AbstractRefreshableApplicationContext, and GenericApplicationContext . Do keep in mind, that different deployments may have slightly different rules, so you need to check that all context implementation and factories actually extend the interfaces and classes mentioned here.
There appear to be the following steps to initializing the ApplicationContext as of ver.6:
At the very start, if there is no work and no beans yet, the
ApplicationContext#isActivewill returnfalse, andApplicationContext#isClosedwill also returnfalse.One of the first things that happens is
ApplicationContext#activevalue is set totrue, so theApplicationContext#isActivenow returnstrueThen, the context's
BeanFactorystarts loading bean definitions. You will unlikely to probe this process in a generic way - all I can conclude that if your factory extends fromDefaultListableBeanFactory, then during this phase it hasSerializationIdproperty set tonull. However, there's not really any guarantees for any other value before or after the loading process.Then, follows the bean-factory-post-processing, which you'll only have a hook-into if you registered a post processor of your own, and set it to lowest priority.
Then follows the bean creation phase for some time, which you may be able to track the completion of by probing whether
ConfigurableListableBeanFactory#isConfigurationFrozenbecametrue. Though even after configuration becomes fronzen, the factory may or may not still post-process lazy singleton initialization.Finally, comes the phase of triggering various on-refresh events - calling in
LifecycleProcessor#onRefresh, and firing theContextRefreshedEventinApplicationEventMulticaster.
As you can see, the application event is actually the most robust way of detecting whether the refresh process is completed, but it's not going to help tracking whether the process is ongoing at the time of calling some methods.
As far as non-invasive methods go, that is about it.
There is one extension point, however - the ApplicationStartup interface. It is an invasive way - as in, you have to initialize it before anything is done to the ApplicationContext, but it allows you to precisely track what phase of loading is happening right now, provided that you write some code to do it.
Below I'm going to provide a wrapper for the original startup object that only allows to track whether the overal refresh process is ongoing. With that you'll be able to check for context startup by calling
context.isActive()
&& ((StartupPhaseTracker) context.getApplicationStartup()).isRefreshInProgress()
The class (i'm going to use lombok annotations to make code shorter):
@lombok.RequiredArgsConstructor
public class StartupPhaseTracker implements ApplicationStartup {
private static final String REFRESH_PHASE_NAME = "spring.context.refresh";
private final ApplicationStartup impl;
@lombok.Getter
private volatile boolean refreshInProgress;
@Override
public StartupStep start(String stepName) {
if (REFRESH_PHASE_NAME.equals(stepName)) {
refreshInProgress = true;
return new RefreshTrackerStep(this, impl.start(stepName));
}
return impl.start(stepName);
}
@lombok.RequiredArgsConstructor
private static class RefreshTrackerStep implements StartupStep {
private final StartupPhaseTracker tracker;
private final @lombok.experimental.Delegate StartupStep impl;
@Override
public void end() {
impl.end();
tracker.refreshInProgress = false;
}
@Override
public StartupStep tag(String key, String value) {
impl.tag(key, value);
return this;
}
@Override
public StartupStep tag(String key, Supplier<String> value) {
impl.tag(key, value);
return this;
}
}
You should register it before anything else is done to the application context, though, which is tricky and depends on how you're going to run it:
ApplicationContext context = ...
context.setApplicationStartup(
new StartupPhaseTracker(context.getApplicationStartup());
2 Comments
SpringApplication::setApplicationStartup and register your implementation at the very startApplicationStartup isn't useable.Why not add a bean to the application context which listens to events.
@Bean
@Slf4j
public class MyEventRegistry {
private final List<ApplicationEvent> events = new ArrayList<>();
@EventListener
public synchronized void onEvent(ApplicationEvent event) {
log.info("Received event {}", event.getClass());
events.add(event);
}
public synchronized void reset() {
events.clear();
}
public synchronized boolean hasEvent(Class<? extends ApplicationEvent> eventType) {
return events.stream().anyMatch(e -> e.getClass().isAssignableFrom(eventType));
}
public synchronized int lastEventIndex(int startIndex, Class<? extends ApplicationEvent> eventType) {
for (int i = events.size() - 1; i >= startIndex; i--) {
if (events.get(i).getClass().isAssignableFrom(eventType)) {
return i;
}
}
return -1;
}
...
}
You could then @Inject MyEventRegistry wherever you need it to query the events that have occurred
Usage
@Bean
@Slf4j
public void MyService
@Inject private ApplicationContext context;
@Inject private MyEventRegistry registry;
public void doStuff() {
int lastStartIndex = registry.lastEventIndex(0, ContextStartedEvent.class);
if (registry.lastEventIndex(lastStartIndex , ContextRefreshedEvent.class) != -1) {
log.info("Context was refreshed after start");
}
if (registry.hasEvent(ContextStoppedEvent.class)) {
log.info("Context was stopped");
}
}
...
}
4 Comments
applicationContext and calculate whether it has been refreshed or not using this bean? Keep in mind that your bean might not yet be created at that moment of the calculation because that's the whole point of this inquiry, figure out if the applicationContext's state is "before a refresh()", "during a refresh()" or "after a full refresh()".ContextRefreshedEvent ( which OP said it can't do ) and having this indirection?Explore related questions
See similar questions with these tags.