[SpringBoot] Beanとは一体何者なのか?

spring

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Springフレームワークに馴染んだ後、SpringBootに触れた方はこのような疑問は持たれないのでしょうが、 僕のようにSpringを知らずにSpringBootを始める方も中にはいるのではないかと思い、僕が非常に悩んだBeanというものについて僕が現状知っていることを書いてみようと思います。

注) 間違っていること、説明が不足していることありましたらご指摘いただければ嬉しいです。

結論

間違いを恐れずに言えばBeanとは

@Beanと書いたメソッドでインスタンス化されたクラスがシングルトンクラスとしてDIコンテナに登録される。任意のクラスで@Autowiredで注入してアクセスできる。

と僕は理解しました。
わかりにくいですね。
僕も文章だけではうまく説明できないので、結論に至るまでの過程を書いていきます。

調査

まず@Beanというものについて調べた時に @Configurationと記述したクラス内のメソッドに@Beanを記述することで@Beanを定義することができ、任意のクラスで@Autowiredで注入することで定義したBeanを使うことができる。

ということで僕が最初に書いたコードがこんな感じです。

※@Scheduledは定期実行するためのアノテーションになります。興味のある方はこちらをどうぞ!

@Configuration
public class CreateBean {

	@Bean
	public String getHoge(){
		return "hoge";
	}
}
@Component
public class BatchProcessing {
	
	@Autowired
	CreateBean createBean;
	
	@Scheduled(initialDelay = 3000, fixedDelay = 5000)
	public void initialDelay(){
		System.out.println(createBean.getHoge());
	}
}

結果は

hoge
hoge
hoge
hoge

試しに@Beanを消してビルドしてもエラーも出ず、同じ結果になります。

じゃあ結局Beanって何なんだ!!
アノテーションなくても動くじゃん!!

バカですいません。

@Beanの正しい使い方は以下のようになります。

public class Count {

	private int count;
	
	public int getCount(){
		return count++;
	}
}

このクラスは、呼ばれるたびに変数countをインクリメントして返すgetCount()メソッドと、インクリメントする変数countだけを持ったシンプルなクラスです。

@Configuration
public class CreateBean {

	@Bean
	public Count getCount(){
		return new Count();
	}
}

ここでCountクラスをDIコンテナに登録しています。

@Component
public class BatchProcessing {

	@Autowired
	Count count;

	@Scheduled(initialDelay = 3000, fixedDelay = 5000)
	public void initialDelay(){
		System.out.println(count.getCount());
	}
}

ここで登録したCountクラスを@Autowiredで注入しています。

0
1
2
3

メソッドに@Beanを記述するという言葉に引っ張られて、普通のStringやintを返すメソッドに@Beanを記述していましたが Stringやintを返すメソッドに@Beanをつけても何の意味もありません。

@Beanを記述したメソッドでインスタンス化したクラスを返して、DIコンテナに登録します。そして任意のクラスで@Autowiredで変数に注入して使うようです。

わかってしまえば簡単なことなのになぜ僕はあんな無駄な時間を。。

さらに

@Component
public class BatchProcessing {

	@Autowired
	OuterBatchProcessing outerBatchProcessing;
	@Autowired
	Count count;

	@Scheduled(initialDelay = 3000, fixedDelay = 5000)
	public void initialDelay(){
		System.out.println(count.getCount() + " : from BatchProcessing");
	}
	@Scheduled(initialDelay = 5000, fixedDelay = 5000)
	public void OuterProcessing(){
		outerBatchProcessing.execute();
	}
}

BatchProcessingクラスを少し直して

@Component
public class OuterBatchProcessing {

	@Autowired
	private Count count;
	
	public void execute(){
		System.out.println(count.getCount() + " : from OuterBatchProcessing");
	}
}

別のクラスからも登録しているBeanを使ってみます。

結果は

0 : from BatchProcessing
1 : from OuterBatchProcessing
2 : from BatchProcessing
3 : from OuterBatchProcessing
4 : from BatchProcessing
5 : from OuterBatchProcessing

@Beanで定義したクラスはデフォルトではシングルトンとしてDIコンテナに格納されるようですね。

最後に

Srpingに馴染んでいる方にとっては当たり前のことなんだろうなと思いながら、SpringBootから始めた僕のような迷い子がいるのではないかと思い書いてみました。 SpringBoot + Gradle の組み合わせで始めるとネットの情報は Spring + Maven の情報が多くわかりにくいので、今後もSpringBoot + Gradle の組み合わせで開発した時のことを書いていこうと思います。

参考

http://www.riem.nagoya-u.ac.jp/~ohta/etc/springboot-5.html

AWS Cloud Roadshow 2017 福岡

  • making

    SpringのDIコンテナに管理されているインスタンスをSpring用語でBeanと言います。
    Beanの代表的な登録方法(Bean定義方法)は

    1. JavaConfig (@Beanを使う方法)
    2. XML (タグを使う方法)
    3. コンポーネントスキャン(@Component + @ComponentScan)

    の3つがあります。

    DIコンテナに登録されたBeanは@Autowiredで注入させることができるようになります。
    あるいはJavaConfigで@Bean BatchProcessing batchProcessing(Count count) { /*…BatchProcessingインスタンスをreturn*/}というようにメソッドの引数に注入させることもできます。
    これらは依存関係の定義に相当します。(定義した依存関係に対して、コンテナが適切なインスタンスを注入してくれることを依存性の解決と言います)

    DIコンテナを使うメリットとして、インスタンスのライフサイクル(スコープ)をコンテナに任せることができる点があります。Bean定義時にスコープを指定できて、デフォルトではシングルトンスコープになりますが、プロトタイプスコープ(毎回新規作成)、セッションスコープ、リクエストスコープなどに変更することも可能です。@Beanや@Componentと一緒に@Scope(“prototype”)とかを指定する形です。
    なので、いつもシングルトンというわけではありません。

    この辺Spring徹底入門の2章にきっちり書いたので是非一読ください。(Spring Bootとは関係のない話です)

    (脱線しますが、サンプルのCountクラスはシングルトンスコープなので++するのはスレッドアンセーフです。AtomicIntegerに変えたほうがいいです。)