🐙

Re:ゼロから始めるSpring Boot #6 Beanの管理

に公開

Beanとは

Beanとは、アプリケーション全体で管理されるオブジェクトのこと。Beanによって、依存性管理やオブジェクトのライフサイクル管理が効率的に行われる。

なぜBeanが重要かというと、Spring Bootではアプリケーションが大規模になっても、オブジェクトの生成や依存関係の管理を自動化することで、開発者の負担を大幅に減らせるからです。Beanは、DI(依存性注入)の仕組みの中心にあり、アプリケーション全体で必要なオブジェクトを適切に共有・管理する役割を担っています。

例えば、@Serviceアノテーションを付けたクラスは、自動的にBeanとして登録されます。次に、コントローラークラスで@Autowiredを使えば、そのサービスクラスのインスタンスを自動的に注入して使うことができます。これにより、わざわざnewしてインスタンスを作る必要がなくなり、コードの保守性が向上します。

つまり、Spring BootにおけるBeanとは、アプリケーションの構成要素を管理するための仕組みであり、開発効率とアプリケーションの品質を高めるために欠かせない存在です。

Beanの管理

Springでは Beanの管理は主に2つのプロセスがある。Spring Bootでは、Beanの管理はほとんど自動化されているため意識する必要はないが、AutoConfigurationを理解するのに必要な知識である。

Beanのスキャン

Springでは、Beanをスキャンする方法は2つあります。

  1. XMLファイルにcomponent-scanタグを記載する方法
    <context:component-scan base-package="com.RezeroSB"/>
    
  2. @ComponentScan アノテーションを付ける方法
    @ComponentScan(base-package="com.RezeroSB")
    

spring Bootでは、意図的にいずれも行っていない、行う必要がない。下記リンク先が例。
https://zenn.dev/willyang/articles/e21f3f245fc860

なぜなら、スタータークラスについている@SpringBootApplicationに含まれているからである。

しかし、@ComponentScanはスキャンされるパッケージの階層は指定されていない。デフォルトではスタータークラスが存在するパッケージ内でスキャンが行われる。パッケージを指定したい場合は、以下の楊に別途記述が必要。

@SpringBootApplication
@ComponentScan(basePackages = "com.rezerosb") //←これ
public class RezeroSb05MybatisApplication {
    public static void main(String[] args) {
        SpringApplication.run(RezeroSb05MybatisApplication.class, args);
    }

}

Beanの登録

Springでは、下記4つのアノテーションを使ってBeanを定義するのが一般的である。

アノテーション 説明 適用場所
@Component Beanを定義する基本的なアノテーション 下記の3つに該当しない場合に使用
@Controller @Componentの派生アノテーション コントローラークラスに付ける
@Service @Componentの派生アノテーション ビジネスロジックを持つクラスに付ける
@Repository @Componentの派生アノテーション データアクセスクラスに付ける(MyBatisと連携するため使用頻度は少なめ)

しかし、登録したいBeanオブジェクトが自作ではない場合は、@Componentやその派生アノテーションではBeanを宣言することはできない。この場合は、@Bean@Importアノテーションを使ってBeanオブジェクトを登録することができる。

Hands-onの準備

1.適当に外部(サードパーティ)のjarパッケージをMavenリポジトリにインストールする。
ソースコードはこちら:https://github.com/Willyangl/ReZeroSpringBoot/tree/497bae769ddd9523500894e4dcca5f41356bff0e/BeanRegisterMaterial
2.Spring Bootのプロジェクトを作成し、1でインストールしたパッケージをpom.xmlにdependencyとして追加する。

@BeanでBeanを登録

@SpringBootApplication
public class ReZeroSb06BeanRegisterApplication {
    public static void main(String[] args) {

        //登録したBeanを確認するためのコード
        ApplicationContext context = SpringApplication.run(ReZeroSb06BeanRegisterApplication.class, args);
        Country country = context.getBean(Country.class);
        System.out.println(country);
        //getBeanはメソッド名を利用できる
        System.out.println(context.getBean("province")); 
    }

    //CountryオブジェをBeanとして登録
    @Bean //メソッドの戻り値をIOCコンテナに管理させ、IOCコンテナのBeanオブジェクトとする
    public Country country(){
        return new Country();
    }

}
実行結果
2025-04-26T12:25:27.201+09:00  INFO 7604 --- [ReZeroSB06-BeanRegister] [  restartedMain] c.r.r.ReZeroSb06BeanRegisterApplication  : Started ReZeroSb06BeanRegisterApplication in 0.378 seconds (process running for 0.626)
Country{name='null', system='null'}  
Province{name='null', direction='null'}

Process finished with exit code 0

CountryとProvinceオブジェクトは問題なく取り出せたね。nullになっているのは、newしたとき値を設定しなかったのが原因なので問題なし。

しかし、コードの可読性やメンテナビリティを考慮し、スタータークラスでBeanを登録するのは不適切であるため、おすすめではない。一般的には、@Configuration クラスで登録することがおすすめ。

@Configuration クラスでBeanを登録

やり方:
スタータークラスが存在するパッケージ内でconfig``パッケージを作成し、その中にCommonConfigクラスを作成する。CommonConfig```クラス内でBeanを登録する。

@Configuration
public class CommonConfig {
    //CountryオブジェをBeanとして登録
    @Bean //メソッドの戻り値をIOCコンテナに管理させ、IOCコンテナのBeanオブジェクトとする
    public Country country(){
        return new Country();
    }
}

これでスタータークラスはすっきりして気持ちいいね。

@ImportでBeanを登録

インポートしたいクラスは少ない場合は、@Importを使ってインポートすればよい。
インポートしたいクラスは複数ある場合は、設定ファイルで一括インポートするといい。

インポートしたいクラスは少ない場合、@Configurationクラスをインポートする方法

スタータークラスの上に@Import(XXX.class)アノテーションを付けることでBeanのインポートができる。補足:インポートのクラスは、スタータークラスと異なるパッケージに配置してもOK。

//@Import(XXX.class)
@SpringBootApplication
@Import(CommonConfig.class) //← 先ほどと同じCommonConfigクラスを使っている。
public class SpringbootRegistApplication {}

@Configurationクラスが複数ある場合は

//@Import(XXX.class)
@SpringBootApplication
@Import({CommonConfig.class,AAA.class,BBB.class}) //← @Configurationクラスを一々書く必要があるのでエレガントじゃないね。
public class SpringbootRegistApplication {}

インポートしたいクラスは複数ある場合、設定ファイルで一括インポートする方法

1.設定ファイルに@Configurationクラスの一括管理する。
2.ImportSelectorインターフェースの実装クラスを使って一括インポートする。

具体的なやり方は以下。
(1).resorceディレクトリー下に、commmon.importsファイルを作成し、インポートしたいクラスのリファレンスを記載する。

(2).ImportSelectorインターフェースの実装クラスを作成

public class CommonImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //設定ファイルの内容を読み取る
        List<String> imports = new ArrayList<>();
        InputStream is = CommonImportSelector.class.getClassLoader().getResourceAsStream("common.imports");
        BufferedReader br = new BufferedReader(new InputStreamReader(is));
        String line = null;
        try {
            while((line = br.readLine())!=null){
                imports.add(line);
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            if (br!=null){
                try {
                    br.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
        return imports.toArray(new String[0]);
    }
}

(3).スタータークラスの上にアノテーションを付ける。

@SpringBootApplication
@Import(CommonImportSelector.class) //←これ
public class ReZeroSb06BeanRegisterApplication {
    public static void main(String[] args) {
//略
    }
}

実行結果:

Country{name='null', system='null'}
Province{name='null', direction='null'}

Process finished with exit code 0

ソースコード:https://github.com/Willyangl/ReZeroSpringBoot/tree/master/ReZeroSB06-BeanRegister

Discussion