0

A similar question has been answered...

Here: Listener for server starup and all spring bean loaded completely

Here: How to add a hook to the application context initialization event?

and several other places.

In my primary application, when the Root Spring context initializes it will trigger three child contexts to initialize. When listening for an event to fire (based on the ContextRefreshedEvent), this results in four total events. This does not give the consumer of these events accurate information regarding the overall state of the application's Spring context.

Unfortunately I don't have the ability to change the primary application. The code that I'm wrestling with now is packaged as a jar and loaded into the primary application using a plugin architecture. I am able to hook into the application's Spring context without issue, and I can successfully receive ContextRefreshEvent triggers.

What I'm looking for is a way to understand if all Spring application contexts in the primary application have completed. One thing I tried was to manually keep track of the starting and completing of the application context (using a map) so that I could tell when all known application contexts were finished initializing. This didn't work for me, as I found that the ContextStartedEvent didn't trigger during Spring's refresh operation.

Additional things that could work for me would be knowing how many application contexts exist or how many beans there are that need to be loaded. That way I could keep track of them as they finished and ultimately know that all application contexts are complete.

Any ideas would be appreciated.

EDIT

In an attempt to ask as pointed a question as possible, I made this question too prescriptive in terms of the implementation. Here is the actual problem that I was trying to solve (in the form of a question):

How might a plugin, packaged as a jar inside of a webapp, tell when the context of the webapp has been deployed successfully in Tomcat?

Through testing, I thought it was fairly safe to assert that the initialization of the Spring context was synonymous with the deploy phase of the application through Tomcat. This may be false, but it lined up well enough.

Broadening the scope of the fix from exclusively using Spring, I was able to come up with a solution to the problem. I will post that as a separate answer.

Bryan Larson
  • 485
  • 1
  • 9
  • 21

2 Answers2

1

As of Spring 2.5.X < 4.X

One thing you could do is listen for ContextStartedEvent-s, add them to a map and then once ContextRefreshedEvent-s are triggered, wait while all contexts are refreshed.

This still might be not safe if the context is refreshed multiple times though...but at least a place to start.

As of Spring 4+

Reading ContextRefreshedEvent javadocs:

Event raised when an {@code ApplicationContext} gets initialized or refreshed.

What this means is that you can have your own logic to track whether that context was initialized or refreshed.

@EventListener( { ContextRefreshedEvent.class } )
void contextRefreshedEvent( ContextRefreshedEvent e ) {
    ApplicationContext context = ( ApplicationContext ) e.getSource();
    System.out.println( "a context refreshed event happened for context: " + context.getDisplayName() );
}

Meaning - the first time you'll get an event with that context - you will know that it was initialized. The following times - you will know that it was refreshed.

Being brutally simple ( no optimizations ) you can do the following:

private final Map<String, String> contextStatuses = new HashMap<>();

@EventListener( { ContextRefreshedEvent.class } )
public void contextRefreshedEvent( ContextRefreshedEvent e ) {
    ApplicationContext context = ( ApplicationContext ) e.getSource();

    if ( !contextStatuses.containsKey( context.getDisplayName() ) ) { // initialized
        contextStatuses.put( context.getDisplayName(), "initialized" );
    } else { // refreshed
        contextStatuses.put( context.getDisplayName(), "refreshed" );
    }

    checkAllContextsRefreshed();
}

private void checkAllContextsRefreshed() {
    for ( String status : contextStatuses.values() ) {
        if ( !"refreshed".equals( status ) ) {
            return;
        }
    }

    doWhenAllContextsRefreshed();
}
Zilvinas
  • 5,518
  • 3
  • 26
  • 26
  • 1
    Hey, thanks for the quick answer! It's quite an essay, but I did attempt to address that in my third paragraph. I think that's a great solution, but Spring doesn't seem to call the start() method, which is necessary to trigger the ContextStartedEvent. It calls the refresh() method instead, which does not trigger the same event. One thing I'm very much interested in is the idea to fake that out. Basically hook into the Spring context init and force the event to be called during the refresh() call. Do you think that would be possible? – Bryan Larson Sep 18 '17 at 20:50
  • As you said *ContextStartedEvent didn't trigger during Spring's refresh operation* - but I meant that it should be fired **once** during application startup, and then *Refresh* events would follow during a refresh. But quite possibly you could figure out the context(s) at the start and try and map to the same context(s) at refresh. – Zilvinas Sep 18 '17 at 22:40
  • Thanks for the edited code sample. Unfortunately I do not receive two ContextRefreshEvent triggers on either a Tomcat restart or redeploy through Tomcat manager. – Bryan Larson Sep 19 '17 at 14:16
0

Per the edit on my question, here is the solution that I went with:

In the plugin that is packaged inside the primary application, I added a check using JMX against the stateName attribute of the WebModule type under Catalina. This isn't the exact code I used, but for simplicity the logic looks like this:

ObjectName name = new ObjectName("Catalina:j2eeType=WebModule,name=//localhost/" + contextPath + ",J2EEApplication=none,J2EEServer=none");
MBeanServer mx = ManagementFactory.getPlatformMBeanServer();

String state = String.valueOf(mx.getAttribute(name, "stateName"));
if (state.equals("STARTED")) {
    LOGGER.warn("SUCCESS!");
}

As I mentioned in the edit, equating Tomcat deploy to Spring context initialization was close, but ultimately it wasn't a fair comparison. Using this JMX endpoint gives me a very accurate representation of the state of the Tomcat deploy.

Bryan Larson
  • 485
  • 1
  • 9
  • 21