Spring Boot 2.1.x の Web アプリを 2.2.x へバージョンアップする ( その13 )( gradle の VM オプションを gradle.proeprties に移行する+spring.main.lazy-initialization を試してみる2 )
概要
記事一覧はこちらです。
Spring Boot 2.1.x の Web アプリを 2.2.x へバージョンアップする ( その12 )( spring.main.lazy-initialization を試してみる ) の続きです。
- 今回の手順で確認できるのは以下の内容です。
参照したサイト・書籍
目次
- gradle の VM オプションを gradle.proeprties に移行する
- ユニットテスト実行時の VM オプションに
-XX:TieredStopAtLevel=1
、-Dspring.main.lazy-initialization=true
を追加する
手順
gradle の VM オプションを gradle.proeprties に移行する
IntelliJ IDEA の設定を見直していた時に Gradle VM options の設定項目が deprecated になっていることに気づきました。代わりに gradle.properties に設定するよう記述されています。
プロジェクトのルートディレクトリ直下に gradle.properties を新規作成し、以下の内容を記述します。
org.gradle.jvmargs=-Dfile.encoding=UTF-8
IntelliJ IDEA の設定項目の値をクリアすると、設定項目自体が消えました。
ユニットテスト実行時の VM オプションに -XX:TieredStopAtLevel=1
、-Dspring.main.lazy-initialization=true
を追加する
Spring Boot 2.1.x の Web アプリを 2.2.x へバージョンアップする ( その12 )( spring.main.lazy-initialization を試してみる ) で spring.main.lazy-initialization
設定時の動作を確認しましたが、ユニットテスト実行時に追加した時の動作確認をし忘れていましたので確認してみます。
build.gradle の jvmArgsForTask 変数に -XX:TieredStopAtLevel=1
、-Dspring.main.lazy-initialization=true
を追加します。testJUnit4AndSpock、test タスクだけでなく bootRun タスク時に付いていても問題ないので、共通の VM オプションを設定している変数 jvmArgsForTask に追加します。
def jvmArgsForTask = [ "-ea", "-Dfile.encoding=UTF-8", "-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP", "-XX:TieredStopAtLevel=1", "-Dspring.main.lazy-initialization=true" ]
Gradle Tool Window の左上にある「Refresh all Gradle projects」ボタンをクリックして更新した後、clean タスク実行 → Rebuild Project 実行 → build タスクを実行すると、テストが1件失敗しました。
失敗したのは以下のテストです。このテストだけ実行してみます。
@Nested @SpringBootTest class 次回から自動的にログインするのテスト { @RegisterExtension @Autowired public TestDataExtension testDataExtension; @RegisterExtension @Autowired public SecurityMockMvcExtension mvc; @Autowired ServletContext servletContext; @Test void 次回から自動的にログインするをチェックすれば次はログインしていなくてもログイン後の画面にアクセスできる() throws Exception { // ログイン前にはログイン後の画面にアクセスできない mvc.noauth.perform(get(Constant.URL_AFTER_LOGIN_FOR_ROLE_ADMIN)) .andExpect(status().isFound()) .andExpect(redirectedUrl("http://localhost/")) .andExpect(unauthenticated()); // 「次回から自動的にログインする」をチェックしてログインし、remember-me Cookie を生成する org.springframework.mock.web.MockHttpServletRequest request = formLogin() .user("id", mvc.MAILADDR_TANAKA_TARO) .password("password", "taro") .buildRequest(servletContext); request.addParameter("remember-me", "true"); SimpleRequestBuilder simpleRequestBuilder = new SimpleRequestBuilder(request); MvcResult result = mvc.noauth.perform(simpleRequestBuilder) .andExpect(status().isFound()) .andExpect(redirectedUrl(Constant.URL_AFTER_LOGIN_FOR_ROLE_ADMIN)) .andExpect(authenticated().withUsername(mvc.MAILADDR_TANAKA_TARO)) .andReturn(); Cookie[] cookie = result.getResponse().getCookies(); // remember-me Cookie を引き継いでログイン後の画面にアクセスするとアクセスできる mvc.noauth.perform(get(Constant.URL_AFTER_LOGIN_FOR_ROLE_ADMIN).cookie(cookie)) .andExpect(status().isOk()) .andExpect(content().contentType("text/html;charset=UTF-8")) .andExpect(model().hasNoErrors()) .andExpect(authenticated().withUsername(mvc.MAILADDR_TANAKA_TARO)); // ログイン画面にアクセスしても有効な remember-me Cookie があればログイン後の画面にリダイレクトする mvc.noauth.perform(get("/").cookie(cookie)) .andExpect(status().isFound()) .andExpect(redirectedUrl(Constant.URL_AFTER_LOGIN_FOR_ROLE_ADMIN)) .andExpect(authenticated().withUsername(mvc.MAILADDR_TANAKA_TARO)); } }
IntelliJ IDEA のエディタから上のテストを1度実行した後(この時は -Dspring.main.lazy-initialization=true
を指定していないので成功します)、メインメニューの「Run」-「Edit Configurations...」を選択します。
「Run/Debug Configurations」ダイアログが表示されたら、JUnit のテストの VM options に -XX:TieredStopAtLevel=1
、-Dspring.main.lazy-initialization=true
を追加します。
IntelliJ IDEA の画面上部から JUnit のテストを選択し実行すると、
今度は失敗しました。java.lang.IllegalStateException: UserDetailsService is required.
のエラーメッセージが出ています。
LazyInitializationExcludeFilter.forBeanTypes(...)
で UserDetailsService や LendingUserDetailsService を Lazy の対象から除外してみましたが、テストは成功しませんでした。
breakpoint を設定して何度も debug 実行して分かったのは以下の点でした。
テストが失敗するのは以下のコードが実行された時でした。
-Dspring.main.lazy-initialization=true
を付けている時と付けない時で WebSecurityConfigurerAdapter$UserDetailsServiceDelegator#loadUserByUsername 内のdelegate = delegateBuilder.getDefaultUserDetailsService();
の動作が異なっていました。-Dspring.main.lazy-initialization=true
を付けている時は、delegateBuilder = AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder
の時にdelegateBuilder.getDefaultUserDetailsService()
は null を返すのですが、-Dspring.main.lazy-initialization=true
を付けていない時は、delegateBuilder = AuthenticationConfiguration$DefaultPasswordEncoderAuthenticationManagerBuilder
の時にdelegateBuilder.getDefaultUserDetailsService()
は ksbysample.webapp.lending.security.LendingUserDetailsService のインスタンスを返します。このアプリで defaultUserDetailsService に影響しそうなのは ksbysample.webapp.lending.config.WebSecurityConfig#configAuthentication しかなかったので breakpoint を設定してみたところ、
-Dspring.main.lazy-initialization=true
を付けている時には breakpoint で止まりませんでした。lazy の対象になっていて起動時に実行されず、後で必要になってからも実行されていないようです。
そうであれば ksbysample.webapp.lending.config.WebSecurityConfig クラス自体を lazy の対象外にすれば解決しそうに思えたので、クラスに @Lazy(false)
アノテーションを追加してみます。
@Lazy(false) @Configuration public class WebSecurityConfig {
その結果、テストの実行は成功し、
build タスクも成功するようになりました。
build タスクの実行時間も Spring Boot 2.1.x の Web アプリを 2.2.x へバージョンアップする ( その4 )( Spring Boot を 2.1.11 → 2.2.2 へバージョンアップする ) の時と比較すると 4分 → 3分 20秒 で 40秒程短くなりました。テストだと結構効果がありますね。
テストはこのまま -XX:TieredStopAtLevel=1
、-Dspring.main.lazy-initialization=true
を追加しておくことにします。
履歴
2020/01/13
初版発行。