Springの@Bean Lite Modeについて

# 概要

Springにおいて、コンテナ（Spring IoC container）にBeanを定義するために`@Configuration`アノテーションを付与したクラスを作成し、その中に`@Bean`を付与したメソッドを作成することはよくあると思います。
例えば以下のようなクラスです。

```java
@Configuration
public class BeanDef1 {

    @Bean
    public Bean1 bean1() {
        Bean1 bean1 = new Bean1();
        return bean1;
    }
}
```

これによって、他のクラスがBean1クラスのインスタンスを要求した場合に返すべきインスタンスを、コンテナは記憶します。

これと同様に`@Component`アノテーションを利用してBean定義をすることも可能です。
例えば以下のようです。

```java
@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

```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

```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

```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:~~~`という文言が１度しか現れていないことから、`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.`と言われます）
![スクリーンショット 2021-06-27 18.03.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441085/7920a2aa-fca9-110a-ca57-9863fa9fd806.png)

## 使い所について

`@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