Edited at

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

More than 1 year has passed since last update.


概要

下記のような構成にて 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 から取得できませんでした。