3

I have an instance of a Spring ApplicationContext in a method and I need to "probe" it and decide if:

  1. 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
  2. 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

  1. A ConfigurableApplicationContext.isRefreshed() method which would indicate if a context has been fully refreshed at least once and
  2. 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.

0

2 Answers 2

1

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:

  1. At the very start, if there is no work and no beans yet, the ApplicationContext#isActive will return false, and ApplicationContext#isClosed will also return false.

  2. One of the first things that happens is ApplicationContext#active value is set to true, so the ApplicationContext#isActive now returns true

  3. Then, the context's BeanFactory starts loading bean definitions. You will unlikely to probe this process in a generic way - all I can conclude that if your factory extends from DefaultListableBeanFactory, then during this phase it has SerializationId property set to null . However, there's not really any guarantees for any other value before or after the loading process.

  4. 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.

  5. Then follows the bean creation phase for some time, which you may be able to track the completion of by probing whether ConfigurableListableBeanFactory#isConfigurationFrozen became true. Though even after configuration becomes fronzen, the factory may or may not still post-process lazy singleton initialization.

  6. Finally, comes the phase of triggering various on-refresh events - calling in LifecycleProcessor#onRefresh, and firing the ContextRefreshedEvent in ApplicationEventMulticaster.

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());
Sign up to request clarification or add additional context in comments.

2 Comments

you can do SpringApplication::setApplicationStartup and register your implementation at the very start
Yes, but the question reads as if from developer of a tool that sits to the side of typical Spring startup, and you aren't able to alter application startup code. Though if that's true, then whole idea with ApplicationStartup isn't useable.
0

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

Thanks for the reply. Can you please add the code block that I would use in my method which will start from the 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()".
Have included sample usage
how is this different than simply listening for ContextRefreshedEvent ( which OP said it can't do ) and having this indirection?
OP said "I cannot use a listener for ContextRefreshedEvent. I can only operate on the applicationContext that was passed to me". I assumed this meant that the execution of the logic could not be driven by spring events. I was thinking perhaps the OP had not considered capturing the spring events via an event listener and then querying them based upon another action (eg service method invocation)

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.