Springの@Bean「名前」入門 - いつ付けるべき?どう効く?優先度は?
Spring Bootで開発していると、@Bean を使う機会はよくありますよね。
そして地味に悩むのが「この@Bean、名前を付けるべき?それとも放置で良い?」という話です。
この記事では、@Bean の名前が何に使われ、いつ明示すべきか、そして複数Beanがある時に“どれが選ばれるのか(優先度)”まで、実務目線で整理します。
@Beanの「名前」とは?
Springでは、コンテナに登録される各Beanに「Bean名(identifier)」があります。
@Bean の場合、このBean名は主に次の用途で登場します。
- 同じ型のBeanが複数あるとき、どれを注入するかの判別
@Qualifier/@Resourceなど“名前ベース”の注入- 条件付きBean登録(
@ConditionalOnBean(name=...)など) - Beanの上書き(オーバーライド)や衝突検出
つまり、名前は「Beanを識別するためのキー」です。
デフォルトの命名ルール
@Bean で名前を指定しない場合、Bean名は「メソッド名」になります。
@Configuration
public class MyConfig {
@Bean
public MyService myService() {
return new MyService();
}
}
この例だと、Bean名は myService です。
メソッド名=Bean名、という単純なルールなので、 「名前を付けていないつもりでも、付いている」点は意識しておくとハマりにくいです。
名前の付け方(明示名・エイリアス)
1つの名前を付ける
@Configuration
public class MyConfig {
@Bean("mainService")
public MyService myService() {
return new MyService();
}
}
@Bean("...") は省略形で、@Bean(name="...") と同じ意味です。
エイリアス(別名)を付ける
1つのBeanに複数の名前を持たせることもできます。
@Configuration
public class MyConfig {
@Bean({"dataSource", "readDataSource"})
public DataSource dataSource() {
// ...
return /* DataSource */;
}
}
この場合、同一Beanを dataSource と readDataSource の両方の名前で参照できます。
(ただし、乱用すると読みづらくなるので、必要なときだけでOKです)
いつ名前を付けるべきか
結論から言うと、基本は「付けない(=メソッド名のまま)」で十分なことが多いです。 ただし、次のような場面では、明示的に名前を付けるメリットが大きいです。
同じ型の@Beanが複数あるとき
例えば MyClient を2種類作りたい、など。
@Configuration
public class ClientConfig {
@Bean("userClient")
public MyClient userClient() {
return new MyClient("https://user-api.example");
}
@Bean("billingClient")
public MyClient billingClient() {
return new MyClient("https://billing-api.example");
}
}
このように、用途が違うBeanが同じ型になるなら「名前で意味を表す」方が後から楽です。
(メソッド名でも表せますが、@Qualifier で使う前提なら、意図が明確になります)
@Qualifier / @Resource を使って“注入先で指定したい”とき
後述しますが、名前は注入の判別手段として非常に強いです。 「この注入は、絶対にこのBean」という意図があるなら、名前を付けておくと安全です。
条件付き(Condition)で“名前”を参照したいとき
Spring BootのAuto Configurationや自作の条件分岐で、Bean名を使うケースがあります。
@ConditionalOnBean(name = "xxx")@ConditionalOnMissingBean(name = "xxx")
このように “名前がAPIになる” 場面では、明示しておくのが無難です。
外部ライブラリ由来のBeanと区別したいとき
外部ライブラリが提供するBeanと同じ型を自前でも定義する、という場面があります。 このとき、名前を分けておくと、注入が読みやすくなります。
注入時の優先度(@Qualifier / @Primary / パラメータ名 など)
ここが一番ややこしいところです。 ポイントは「型で候補を集めた後、どれを1つに決めるか」です。
まず基本:@Autowired は “型” が起点
@Service
public class MyUseCase {
private final MyClient client;
public MyUseCase(MyClient client) {
this.client = client;
}
}
MyClient のBeanが1つならこれで決まります。
しかし複数あると、どれを入れるか決められずエラーになります。
@Qualifier があると最優先(実務ではこれが一番強い)
@Service
public class MyUseCase {
private final MyClient client;
public MyUseCase(@Qualifier("billingClient") MyClient client) {
this.client = client;
}
}
@Qualifier("billingClient") は「候補を絞る」ための指定です。
同じ型が何個あっても、名前(またはQualifier)で狙い撃ちできます。
@Primary は “デフォルトの1個” を決めたいときに有効
@Configuration
public class ClientConfig {
@Bean
@Primary
public MyClient primaryClient() {
return new MyClient("https://default.example");
}
@Bean("billingClient")
public MyClient billingClient() {
return new MyClient("https://billing-api.example");
}
}
@Primary は「複数候補があるなら、これを基本採用にする」という意思表示です。
ただし、注入側で @Qualifier を付けた場合は、そちらが優先されます。
(“デフォルトはこっち、特別な時だけQualifierで指定”という設計がしやすいです)
パラメータ名(変数名)がヒントになることがある
Springは、型が複数ある場合に、注入ポイントの名前(フィールド名・引数名)とBean名が一致すると解決できることがあります。
@Service
public class MyUseCase {
private final MyClient billingClient;
public MyUseCase(MyClient billingClient) {
this.billingClient = billingClient;
}
}
billingClient という名前のBeanがあれば、これで決まるケースがあります。
ただし、コンパイル設定や状況によって読み取りの前提が絡むこともあるため、
実務では「明示したいなら @Qualifier を付ける」方が堅いです。
@Resource は “名前優先” の注入
@Resource は、基本的に名前で解決しにいきます。
@Service
public class MyUseCase {
@Resource(name = "billingClient")
private MyClient client;
}
@Autowired とノリが違うので、混ぜると混乱しやすいです。
(使うなら「名前注入」と割り切って使うのが良いです)
「名前の衝突」とBeanオーバーライドの注意点
@Bean の名前はユニークである必要があります。
同じ名前のBean定義が複数登録されると、Spring Bootでは原則エラーになります(オーバーライド無効がデフォルト)。
例えば、別の設定クラスでも同名メソッドを定義してしまうと衝突します。
@Configuration
public class AConfig {
@Bean
public MyService myService() { return new MyService(); }
}
@Configuration
public class BConfig {
@Bean
public MyService myService() { return new MyService(); }
}
「型」ではなく「名前」が同じなので、ここで事故ります。
この事故を避ける一番シンプルな方法は、 “意図が違うなら名前(またはメソッド名)を変える” です。
注意
Tips: Bean名は定数化して使うのがおすすめ
@Qualifier("billingClient") のように文字列を直書きすると、タイポやリネーム時の事故が起きやすくなります。
実務では Bean名を定数として定義し、それを参照する 形にしておくと安全です。
public final class BeanNames {
private BeanNames() {}
public static final String BILLING_CLIENT = "billingClient";
public static final String USER_CLIENT = "userClient";
}
@Configuration
public class ClientConfig {
@Bean(BeanNames.USER_CLIENT)
public MyClient userClient() {
return new MyClient("https://user-api.example");
}
@Bean(BeanNames.BILLING_CLIENT)
public MyClient billingClient() {
return new MyClient("https://billing-api.example");
}
}
@Service
public class MyUseCase {
private final MyClient client;
public MyUseCase(@Qualifier(BeanNames.BILLING_CLIENT) MyClient client) {
this.client = client;
}
}
このようにしておくと、Bean名の変更が必要になった場合でも、修正箇所を1箇所(定数)に集約できます。
まとめ
@BeanのBean名は、デフォルトで「メソッド名」- 名前を付けるべき代表例は「同型Beanが複数」「名前で注入したい」「条件で名前参照したい」
- 注入の優先度は、ざっくり言うと
@Qualifier(指定が強い)@Primary(デフォルトの1つを決める)- 名前一致(パラメータ名などで決まることがある)
- 名前の衝突はSpring Bootだと起動エラーになりやすいので、同名@Beanには注意
@Beanの名前は、普段は意識しなくても動く一方で、 “複数Beanが出てきた瞬間に一気に重要になる”ポイントです。
迷ったら、
- 「デフォルトは @Primary」
- 「明示したい箇所は @Qualifier」
- 「文字列直書きは避け、定数化する」 この3#つを軸にすると設計が安定しやすいです。