サンプルコードで身につける: ApplicationContext と Bean 定義
解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。
1自分のクラスが、どんな名前の Bean になったか
に自分の を並べさせた出力です。 を付けたも countApp という になっています。名前を付けなければ名の先頭を小文字にしたものになり、("renamed") と書いた URLParser は renamed になりました。
context class = org.springframework.boot.web.server.servlet.context.AnnotationConfigServletWebServerApplicationContextbeanDefinitionCount = 148mine: countApp -> l11.count.CountAppmine: greetingService -> l11.count.GreetingServicemine: HTMLRenderer -> l11.count.HTMLRenderermine: renamed -> l11.count.URLParsermy beans = 4
2Bean 名の規則を素の Java で確かめる
上の出力で HTMLRenderer の先頭が小文字になっていないのは、先頭の2文字がどちらも大文字ならそのままにする規則があるためです。 のリファレンスは、これが の java.beans.Introspector.decapitalize と同じ規則だと書いています。そのを直接呼ぶと、同じ結果になります。
3起点の兄弟パッケージは拾われない
メインクラスは l11.scan.app にあり、同じの InsideService と、兄弟パッケージ l11.scan.other の OutsideService の両方に @Component が付いています。起動はエラーなく終わりますが、OutsideService は Bean として存在しません。拾われなかったことは、尋ねて初めて分かります。
insideService : trueoutsideService : false
// l11/scan/app/ScanApp.java
package l11.scan.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication // 起点は l11.scan.app
public class ScanApp {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(ScanApp.class, args);
System.out.println("insideService : " + ctx.containsBean("insideService"));
System.out.println("outsideService : " + ctx.containsBean("outsideService"));
ctx.close();
}
}
// l11/scan/app/InsideService.java … package l11.scan.app; @Component public class InsideService {}
// l11/scan/other/OutsideService.java … package l11.scan.other; @Component public class OutsideService {}
4うまくいかない場合: スキャン外のクラスを注入しようとした
起点の外にある OutsideService を、起点の中の OrderService がで要求した場合の起動ログです(終了コード 1、途中の行は省略)。Description の行が、どのコンストラクタの何番目の引数で、どの型の Bean が見つからなかったかを示しています。クラスは存在し @Component も付いているので、ソースだけでは原因に気づきにくい失敗です。
Error starting ApplicationContext. To display the condition evaluation report re-run your application with 'debug' enabled....***************************APPLICATION FAILED TO START***************************Description:Parameter 0 of constructor in l11.scanfail.app.OrderService required a bean of type 'l11.scanfail.other.OutsideService' that could not be found.Action:Consider defining a bean of type 'l11.scanfail.other.OutsideService' in your configuration.
5同じ型の Bean が2つあるとき、型だけでは取り出せない
Notifier というの実装を2つ、どちらも @Component にした場合です。名前を指定すれば取り出せますが、型だけを渡すと、候補が2つあって1つに決められないというになり、メッセージの末尾に候補の Bean 名が並びます。コンストラクタでの注入で同じ状況にすると、起動そのものが失敗しました。
by name: MailNotifierorg.springframework.beans.factory.NoUniqueBeanDefinitionExceptionNo qualifying bean of type 'l11.dup.Notifier' available: expected single matching bean but found 2: mailNotifier,slackNotifier
// l11/dup/DupApp.java(import は省略)
package l11.dup;
@SpringBootApplication
public class DupApp {
public static void main(String[] args) {
ConfigurableApplicationContext ctx = SpringApplication.run(DupApp.class, args);
// 名前で取り出す
System.out.println("by name: " + ctx.getBean("mailNotifier").getClass().getSimpleName());
try {
ctx.getBean(Notifier.class); // 型で取り出す
} catch (NoUniqueBeanDefinitionException e) {
System.out.println(e.getClass().getName());
System.out.println(e.getMessage());
}
ctx.close();
}
}
interface Notifier {}
@Component
class MailNotifier implements Notifier {}
@Component
class SlackNotifier implements Notifier {}