JUnit
spring-boot
Flyway

Flyway の DDL を main/resources/migration に配置すると AnnotationConfigApplicationContext のインスタンス化に時間を要する

概要

下記のような構成にて JUnit にてテストを実施した際に、表題の現象が発生いたしました。

src
  |- main 
  |     |- resources
  |     |      |- db.migration/V1__createSchema.sql (flyway のマイグレーションDDL)
  |     |- application-development.properties
  |- test 
  |     |- resources
  |           |- application-unit.properties
application-unit.properties
flyway.schemas=PUBLIC
flyway.locations=filesystem:src/main/resources/db/migration/
EnvironmentHelper.java
// DI コンテナにて管理していないクラスからも application.properties にて定義された値を取得できるようにする
public final class EnvironmentHelper {

    private static Environment environment;
    static {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Application.class);
        environment = context.getEnvironment();
    }
}

補足
下記の構成のように test 配下に flyway のリソースを配置すると早かった、、、気がします。。。

src
  |- main 
  |     |- resources
  |     |- application-development.properties
  |- test 
  |     |- resources
  |     |      |- db.migration/V1__createSchema.sql
  |            |- application-unit.properties

また、main, test 配下に flyway の DDL を配置すると、flyway のファイルが 2 つ存在するために起動時エラーが発生する動作となりました。

原因についての推測

下記のようなログが複数回出力されていることから、ApplicationContext を初期化する際、ディレクトリ内の class ファイルをサーチしているものと考えられます。

DEBUG-2017-09-15 15:45:03,474---org.springframework.core.io.support.PathMatchingResourcePatternResolver-775-main-Searching directory [/Users/hoge/root/out/test/classes/example] for files matching pattern [/Users/hoge/root/out/test/classes/example/**/*.class]
DEBUG-2017-09-15 15:45:03,505---org.springframework.core.io.support.PathMatchingResourcePatternResolver-775-main-Searching directory [/Users/hoge/root/out/test/classes/example/helper] for files matching pattern [/Users/hoge/root/out/test/classes/example/**/*.class]
DEBUG-2017-09-15 15:45:03,537---org.springframework.core.io.support.PathMatchingResourcePatternResolver-775-main-Searching directory [/Users/hoge/root/out/test/classes/example/service] for files matching pattern [/Users/hoge/root/out/test/classes/example/**/*.class]

… many similar logs ….

上記ログが複数回出力されます。

回避策

こちら の記事を拝見させていただき、ApplicationListener を使用して、Environment オブジェクトを取得して、静的にアクセスできるようにしました。
また、アプリケーションの起動にてハンドリングされるイベントを調査したところ、下記の順にてログが出力されたことから、ApplicationReadyEvent クラスにて上記処理を実施いたしました。

output.log
INFO -example.spring.ApplicationEventHandler-17-main-org.springframework.boot.autoconfigure.jdbc.DataSourceInitializedEvent
INFO -example.spring.ApplicationEventHandler-17-main-org.springframework.context.event.ContextRefreshedEvent
INFO -example.spring.ApplicationEventHandler-17-main-org.springframework.boot.context.event.ApplicationReadyEvent
INFO -example.spring.ApplicationEventHandler-17-main-org.springframework.boot.autoconfigure.jdbc.DataSourceInitializedEvent
INFO -example.spring.ApplicationEventHandler-17-main-org.springframework.context.event.ContextRefreshedEvent
ApplicationEventHandler.java
@Component
@Slf4j
public class ApplicationEventHandler implements ApplicationListener<ApplicationReadyEvent> {

    @Override
    public void onApplicationEvent(ApplicationReadyEvent event) {
        log.info("application is ready to start...");
        Environment environment = event.getApplicationContext().getEnvironment();
        EnvironmentHelper.setEnvironment(environment);
    }
}

参考情報

タイトル : Interface ApplicationListener
アドレス : https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/ApplicationListener.html

タイトル : Class ApplicationReadyEvent
アドレス : https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/context/event/ApplicationReadyEvent.html

補足

上記の方針を採用したおかげで、環境ごとに用意していた @Profile を定義したクラスは不要かと思いましたが、テスト用の Bean を Injectionするために以前として下記のようなクラスが必要でした。

TestEnvironment.java
@TestConfiguration
@Profile("test")
@PropertySource("classpath:application-test.properties")
public class TestEnvironment {
}

おそらく下記のように、テストクラスの基底クラスにて Bean を取得しているためであると思います。

AbstractBaseTest.java
@SpringBootTest(classes = Application.class)
@RunWith(SpringJUnit4ClassRunner.class)
@ActiveProfiles("test")
public abstract class AbstractBaseTest {
    @Rule
    //public にしないと怒られる
    public TestResource testResource;

    public void setUp() {
        ApplicationContext context = new AnnotationConfigApplicationContext(TestConfig.class);
        testResource = context.getBean(TestResource.class);
    }
}

@Autowired でなぜかテストクラスにテスト用のモジュールをバインドできなかったために下記のように実装しております。。TestResource を Bean に登録するクラス内にて Environment を Autowired しておりますが、@Profile クラスが存在しない場合、アプリケーション開始時に期待するキー項目を Environment から取得できませんでした。