Search code examples
javaspringhibernatekotlinflyway

Spring - Check config properties on application start, before anything else is initialized


I want to execute a function to check the database config before anything else is initialized. However, no matter what method I used, it is always called after some other components or services, which is not ideal. Is it possible to set its priority globally to be the first component created after context initialization?

I've tried the following, and this implementation below is the closest to what I want, but still not quite--as it is executed after the Flyway database executor:

@Configuration
class DataSourceConfig(
    @Value("\${spring.datasource.url}") private val databaseUrl: String
) {
    init {
        if (databaseUrl.lowercase().contains("jdbc:sqlite:", ignoreCase = true)) {
            err.println("!!! ERROR !!!\n" +
                "SQLite isn't supported in the v1 development branch yet.\n" +
                "Please either switch to MariaDB or wait for the stable v1 release.\n")
            exitProcess(1)
        }
    }
}

executed after Flyway

Here are the other options that I tried:

  1. @PostConstruct, executing at the same time as the option below:
@Configuration
class DataSourceConfig(
    private val dataSource: DataSource
) {
    @PostConstruct
    fun checkDataSource() {
        val databaseUrl = JdbcUtils.extractDatabaseMetaData(dataSource) { it.url }
        // ...
    }
}
  1. Adding @EventListener(ApplicationReadyEvent::class). This executes very lately, when everything else is initialized, which is not what I want.
@Configuration
class DataSourceConfig(
    @Value("\${spring.datasource.url}") private val databaseUrl: String
) {
    @EventListener(ApplicationReadyEvent::class)
    fun checkDataSource() {
        // ...
    }
}
  1. Using @EventListener(ContextRefreshedEvent::class). In my testing, this event wasn't called at all, and the application started without calling the check, which led to a very confusing error message.

  2. Using the CommandLineRunner bean, still executing when everything has already initialized.

@Configuration
class DataSourceConfig {
    @Bean
    fun checkDataSource(dataSource: DataSource) = CommandLineRunner {
        val databaseUrl = JdbcUtils.extractDatabaseMetaData(dataSource) { it.url }

        // ...
    }
}

Solution

  • According to the documentation org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent event may fit your requirements. It is fired as soon as environment is available and you can access it with getEnvironment() event's method.

    Note, as this event is fired at such early stage, you will not be able to catch it with @EventListener annotated method. You will need to implement ApplicationListener<ApplicationEnvironmentPreparedEvent> interface and register it manually in your main method. Something like this

    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(App.class);
        springApplication.addListeners((ApplicationListener<ApplicationEnvironmentPreparedEvent>) event -> {
            ConfigurableEnvironment environment = event.getEnvironment();
            //check or modify environment here
        });
        springApplication.run(args);
    }