Guillaume
Guillaume

Reputation: 5557

How to order spring @PostConstruct calls from different jars?

I can't find a way to order the execution of post-init methods in Spring.

I need to order these methods because one is populating data used by the others init methods.

In the spring context I have a test component that is used only to init an in-memory db when it is needed. This component is defined in a dependency.

@Profile({"INMEMORY"})
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class InMemoryDatabaseInitializer {
  @PostConstruct
  private void setupInMemoryDb() {
     // create db schema and populate it with test data into the inmemory db
  } 
}

I have a configuration object that is tested with this in memory profile and that perform a post construct operation that checks some flags in the db:

@Configuration
public class MyConfiguration {
  @PostConstruct
  private void checkDbFlags() {
    // perform some SQL selects using JPA
  }
}

With this setup, the MyConfiguration instance is resolved first and the post-init method checkDbFlags() is called before the in memory db is initialized by the post-init method setupInMemoryDb() and the test crashes.

I have also tried to move the db init method in the constructor this way:

@Profile({"INMEMORY"})
@Order(Ordered.HIGHEST_PRECEDENCE)
@Configuration
public class InMemoryDatabaseInitializer {
  public InMemoryDatabaseInitializer() {
    // perform some SQL selects using JPA
  }
}

but even in this case, the Configuration post-init method checkDbFlags() is called first and crashs because the db structure is not yet installed.

I don't want to add a direct dependency between MyConfiguration and the InMemoryDatabaseInitializer, so I hoped that the @Order in front of the @Configuration would do the trick, but it doesn't. The issue occurs only when the post-init method for the db setup is located in another jar than the one using the db.

A demo application showing this behavior is available here: https://github.com/guame/so_spring_postinit_issue

You can run the com.example.demo.DemoApplicationTests to highlight the issue. Here is a sample log of the current execution:

2020-01-07 09:38:47.035 com.example.demo.DemoApplicationTests    : Starting DemoApplicationTests
2020-01-07 09:38:47.036 com.example.demo.DemoApplicationTests    : The following profiles are active: INMEMORY
2020-01-07 09:38:47.410 com.example.demo.DbInitializerFromApp    : add DATA_FROM_APP into db
2020-01-07 09:38:47.415 yConfig$$EnhancerBySpringCGLIB$$1806b9e8 : checking the flags
2020-01-07 09:38:47.415 yConfig$$EnhancerBySpringCGLIB$$1806b9e8 : checkFlag from current app: DATA_FROM_APP
2020-01-07 09:38:47.415 yConfig$$EnhancerBySpringCGLIB$$1806b9e8 : checkFlag from dependencies jar: null
2020-01-07 09:38:47.418 c.example.demodata.DbInitializerFromJar  : add DATA_FROM_DEPENDENCY into db
2020-01-07 09:38:47.476 com.example.demo.DemoApplicationTests    : Started DemoApplicationTests in 0.802 seconds (JVM running for 1.942)

The flow is correct for the Initializer located in the application, but not for the Initializer located in a dependency.

How can I force the order of the post-init methods so the resolution of some of my initializing beans are resolved before the other even if they are located in dependencies ?

Upvotes: 0

Views: 265

Answers (2)

Lyubomir Papazov
Lyubomir Papazov

Reputation: 165

I think the @DependsOn annotation might help in this case.

Upvotes: 0

Marc Stroebel
Marc Stroebel

Reputation: 2357

Using ApplicationListener or EventListener you can get notified when the complete Spring context has been initialized. If it's sufficent call your checkDbFlags() method from there...

@Component
public class StartupApplicationListenerExample implements
  ApplicationListener<ContextRefreshedEvent> {

    @Override public void onApplicationEvent(ContextRefreshedEvent event) {
        checkDbFlags();
    }
}

https://www.baeldung.com/running-setup-logic-on-startup-in-spring

Upvotes: 1

Related Questions