Spring Boot v2.2.0
Spring Boot v2.2.0がリリースされ、GAとなりました。
なので、その機能の一つであるLazyInitializationについて検証してみます。
Spring Boot 2.2.0
Release v2.2.0.RELEASE · spring-projects/spring-boot · GitHub
Spring Boot 2.2 Release Notes · spring-projects/spring-boot Wiki · GitHub
Spring Boot Reference Documentation
環境
- Windows10
- OracleJDK 13(build 13)
- Spring Boot 2.2.0 RELEAS
Projectの作成
ひながたとなるProjectは、Spring Initializrから作成します。
Gradle
Spring Initializrで作成したGradleプロジェクトでは、Gradle 5.6.2が適用されることを確認しました。
> gradlew -version
------------------------------------------------------------
Gradle 5.6.2
------------------------------------------------------------
Build time: 2019-09-05 16:13:54 UTC
Revision: 55a5e53d855db8fc7b0e494412fc624051a8e781
Kotlin: 1.3.41
Groovy: 2.5.4
Ant: Apache Ant(TM) version 1.9.14 compiled on March 12 2019
JVM: 13-ea (Oracle Corporation 13-ea+33)
OS: Windows 10 10.0 amd64
Lazy Initialization
ApplicationContextで管理するBeanの生成を、アプリケーションの起動時(≒ApplicationContextの初期化時)ではなくて、対象のBeanの呼び出しがされる際に行うのが、Lazy Initializationです。
この機構は以前のバージョンからもありますが、v2.2.0からはpropertyspring.main.lazy-initialization
にtrueを適用することで、一律LazyInitializationが適用されます。
- application.yml
spring:
main:
lazy-initialization: true
- Bean class
package jp.co.musako.domain.model;
public class LazyInitializationDemoBean {
private String message;
public LazyInitializationDemoBean(String message) {
this.message = message;
System.out.println("call constructor " + this.message);
}
public void writer(String message){
System.out.println(this.message + " is " + message);
}
}
- config class
package jp.co.musako.config;
import jp.co.musako.domain.model.LazyInitializationDemoBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class Config {
@Bean("lazyInitializationDemoBean1")
public LazyInitializationDemoBean lazyInitializationDemoBean1(){
return new LazyInitializationDemoBean("create lazyInitializationDemoBean1");
}
@Bean("lazyInitializationDemoBean2")
public LazyInitializationDemoBean lazyInitializationDemoBean2(){
return new LazyInitializationDemoBean("create lazyInitializationDemoBean2");
}
}
- main class
package jp.co.musako;
import jp.co.musako.domain.model.LazyInitializationDemoBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
System.out.println("Initialize Application Context");
LazyInitializationDemoBean bean1 = ctx.getBean("lazyInitializationDemoBean1", LazyInitializationDemoBean.class);
bean1.writer("first bean");
LazyInitializationDemoBean bean2 = ctx.getBean("lazyInitializationDemoBean2", LazyInitializationDemoBean.class);
bean2.writer("second bean");
}
}
実行結果
>gradlew bootRun
> Task :bootRun
2019-10-23 18:16:37.620 INFO 12708 --- [ main] jp.co.musako.Application : Started Application in 4.295 seconds (JVM running for 4.933)
Initialize Application Context
call constructor lazyInitializationDemoBean1
lazyInitializationDemoBean1 is first bean
call constructor lazyInitializationDemoBean2
lazyInitializationDemoBean2 is second bean
ここで、以下の順序で処理がされていることがわかります。
- アプリケーション起動
- 起動後に「Initialize Application Context」を出力
- Beanの生成
- Beanのコンストラクタ内で「call constructor lazyInitializationDemoBean1」を出力
- 生成したBeanのwriterメソッドが呼び出される
- writerメソッドで「lazyInitializationDemoBean1 is first bean」を出力
Lazy Initializationの適用除外
spring.main.lazy-initialization=true
を設定してLazyInitializationをプロジェクトに適用しつつ、特定のBeanをLazyInitializationの対象外とするには以下の方法があります。
-
@org.springframework.context.annotation.Lazy(false)
をBeanに設定する -
org.springframework.boot.LazyInitializationExcludeFilter
に対象外とするBeanを登録する
- config class
package jp.co.musako.config;
import jp.co.musako.domain.model.ExcludeLazyInitializationDemoBean;
import jp.co.musako.domain.model.LazyInitializationDemoBean;
import org.springframework.boot.LazyInitializationExcludeFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
@Configuration
public class Config {
@Bean
static LazyInitializationExcludeFilter integrationLazyInitExcludeFilter() {
return LazyInitializationExcludeFilter.forBeanTypes(ExcludeLazyInitializationDemoBean.class);
}
// Lazy Initialization適用対象
@Bean("lazyInitializationDemoBean1")
public LazyInitializationDemoBean lazyInitializationDemoBean1(){
return new LazyInitializationDemoBean("lazyInitializationDemoBean1");
}
// Lazy Initialization適用対象外
@Bean("lazyInitializationDemoBean2")
@Lazy(false)
public LazyInitializationDemoBean lazyInitializationDemoBean2(){
return new LazyInitializationDemoBean("lazyInitializationDemoBean2");
}
// Lazy Initialization適用対象外
@Bean
public ExcludeLazyInitializationDemoBean ExcludeLazyInitializationDemoBean(){
return new ExcludeLazyInitializationDemoBean("excludeLazyInitializationDemoBean");
}
}
- main class
package jp.co.musako;
import jp.co.musako.domain.model.ExcludeLazyInitializationDemoBean;
import jp.co.musako.domain.model.LazyInitializationDemoBean;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(Application.class, args);
System.out.println("Initialize Application Context");
LazyInitializationDemoBean bean1 = ctx.getBean("lazyInitializationDemoBean1", LazyInitializationDemoBean.class);
bean1.writer("first bean");
LazyInitializationDemoBean bean2 = ctx.getBean("lazyInitializationDemoBean2", LazyInitializationDemoBean.class);
bean2.writer("second bean");
ExcludeLazyInitializationDemoBean ExcludeLazyInitializationBean = ctx.getBean("ExcludeLazyInitializationDemoBean", ExcludeLazyInitializationDemoBean.class);
ExcludeLazyInitializationBean.writer("third bean");
}
}
- 実行結果
>gradlew bootRun
> Task :bootRun
call constructor lazyInitializationDemoBean2
call constructor excludeLazyInitializationDemoBean
2019-10-23 18:52:52.464 INFO 6004 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2019-10-23 18:52:52.468 INFO 6004 --- [ main] jp.co.musako.Application : Started Application in 2.978 seconds (JVM running for 3.454)
Initialize Application Context
call constructor lazyInitializationDemoBean1
lazyInitializationDemoBean1 is first bean
lazyInitializationDemoBean2 is second bean
excludeLazyInitializationDemoBean is third bean
上記のように、Config.javaで定義した3つのBeanのうち、lazyInitializationDemoBean1のみがLazyInitializationの対象となることがわかります。
ソースコードはこちら:GitHub - forests-k/spring-boot-lazy-initialization
: Spring Boot v2.2.0 lazy-initialization sample
Lazy Initializationの適用時の検討事項
Lazy Intializationを導入するメリットは、アプリケーション起動時の速度が向上すること、必要な時に初期化するため無駄にメモリを消費しないことにあります。
しかし、必要な時にBeanを初期化してコンテナにBeanを保持するということは、これまでのBeanのライフサイクルが一部変更となり、コンテナ内で保持するBeanの最大となりうる数量(=メモリ占有量)がみえないことがあるので、CPUへの負荷やロードアベレージへの影響がある、すなわちLazyInitializationを過信しすぎてはいけない、という可能性があるかもしれないです。
そのため、テスト環境や本番同等の環境で一時的にLazy Initializationを無効化するなどのテストを実施することも視野に入れるべきと思われます。