概要
Springにおいて、コンテナ(Spring IoC container)にBeanを定義するために@Configuration
アノテーションを付与したクラスを作成し、その中に@Bean
を付与したメソッドを作成することはよくあると思います。
例えば以下のようなクラスです。
@Configuration
public class BeanDef1 {
@Bean
public Bean1 bean1() {
Bean1 bean1 = new Bean1();
return bean1;
}
}
これによって、他のクラスがBean1クラスのインスタンスを要求した場合に返すべきインスタンスを、コンテナは記憶します。
これと同様に@Component
アノテーションを利用してBean定義をすることも可能です。
例えば以下のようです。
@Component
public class BeanDef1 {
@Bean
public Bean1 bean1() {
Bean1 bean1 = new Bean1();
return bean1;
}
}
Spring公式ドキュメントでは、@Configuration
以外でのBean定義のことをlite mode
と言っています。
ここでは、
-
@Configuration
でのBean定義 -
lite mode
でのBean定義
の違いについて記述していきます。
結論
@Bean
を付与したメソッドにて、同じクラスの@Bean
メソッドを呼び出す際の挙動が違います。
@Configuration
を付与したクラスの場合はコンテナ管理されたインスタンスを返す一方、 lite mode
の場合は通常の同クラス内のメソッドを呼び出すかの如くインスタンスを生成することになります。
公式ドキュメントにも下記のような記述があります。(https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-java-basic-concepts)
Unlike full @Configuration, lite @Bean methods cannot declare inter-bean dependencies.
Instead, they operate on their containing component’s internal state and, optionally, on arguments that they may declare.
Such a @Bean method should therefore not invoke other @Bean methods.
Each such method is literally only a factory method for a particular bean reference, without any special runtime semantics.
The positive side-effect here is that no CGLIB subclassing has to be applied at runtime,
so there are no limitations in terms of class design (that is, the containing class may be final and so forth).
検証
文字ベースでは理解しにくかったので、下記のようなJavaコードを動かし、どのような出力をするか見てみました。
Main.java
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext
= new AnnotationConfigApplicationContext(BeanDef1.class, BeanDef2.class);
Bean1 bean1 = applicationContext.getBean(Bean1.class);
System.out.printf("Bean1 instance from container:%s%n", bean1.hashCode());
Bean2 bean2 = applicationContext.getBean(Bean2.class);
System.out.printf("Bean2 instance from container:%s%n", bean2.hashCode());
applicationContext.close();
}
}
Bean1Def.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class BeanDef1 {
@Bean
public Bean1 bean1() {
Bean1 bean1 = new Bean1();
System.out.printf("Bean1 instance in bean1() method: %s %n", bean1.hashCode());
return bean1;
}
@Bean
public String notBean1() {
Bean1 bean1 = bean1();
System.out.printf("Bean1 instance in notBean1() method:%s%n", bean1.hashCode());
return bean1.getMessage();
}
}
Bean2Def.java
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
@Component
public class BeanDef2 {
@Bean
public Bean2 bean2() {
Bean2 bean2 = new Bean2();
System.out.printf("Bean2 instance in bean2() method: %s %n", bean2.hashCode());
return bean2;
}
@Bean
public String notBean2() {
Bean2 bean2 = bean2();
System.out.printf("Bean2 instance in notBean2() method:%s%n", bean2.hashCode());
return bean2.getMessage();
}
}
出力は下記のようになります。
Bean1 instance in bean1() method: 850551034
Bean1 instance in notBean1() method:850551034
Bean2 instance in bean2() method: 1014982340
Bean2 instance in bean2() method: 424398527
Bean2 instance in notBean2() method:424398527
Bean1 instance from container:850551034
Bean2 instance from container:1014982340
@Configuration
クラスで定義したBean1のインスタンスのハッシュコードは全て850551034
になっていることがわかります。
また、Bean1 instance in bean1() method:~~~
という文言が1度しか現れていないことから、notBean1()
メソッド内のbean1()
メソッドの実行では通常の同クラス内メソッドの実行ではなく、コンテナからのインスタンス取得となっていることがわかります。
一方で@Component
クラスで定義したBean2のインスタンスは、コンテナに格納されているものが1014982340
であり、notBean2()
メソッド内のbean2()
メソッドで通常の同クラス内のメソッド実行が行われていることが分かります。
検証コードは下記に配置しています。
https://github.com/nannany/try-spring-di
ちなみにIntelliJ IDEAでコーディングをしていると下記のように@Component
クラス内での同クラス内メソッド呼び出しは警告が出ます。(Method annotated with @Bean is called directly. Use dependency injection instead.
と言われます)
使い所について
@Bean
付与メソッドでの内部@Bean
付与メソッド呼び出しは、コンテナにあるインスタンスの返却が期待されるので、lite mode
の方の挙動が想定外であり、バグの原因となり得ます。
そのため、基本的には@Configuration
の方を付与すべきだと思います。
ではlite mode
はどのような時に使うべきなのでしょうか?
lite mode
はその名の通り、@Configuration
を付与する場合に比べて軽い処理となります。
https://github.com/spring-projects/spring-boot/issues/9068
そのため、Springアプリケーションの立ち上げ秒数がシビアに求められるときなどが使い所かと思います。
参考
https://radiochemical.hatenablog.com/entry/2020/01/08/200000
https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#beans-factorybeans-annotations