3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Springの@Bean Lite Modeについて

Last updated at Posted at 2021-06-27

概要

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.と言われます)
スクリーンショット 2021-06-27 18.03.36.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

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?