---
title: SpringのBean定義(Java Config)で型が重複する場合のインジェクション方法
tags: ["Java", "Spring"]
categories: ["Programming", "Java", "org", "springframework", "core"]
date: 2016-03-09T01:54:01Z
updated: 2016-03-09T01:57:53Z
---

Spring DIの基本的な話ですが、よくはまっている人を見るので書いておきます。

例として次のJavaConfigがある場合を考えてみましょう。

``` java
@Configuration
@ComponentScan
public class AppConfig {

    @Bean
    PasswordEncoder sha256PasswordEncoder() {
        return new Sha256PasswordEncoder();
    }

    @Bean
    PasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // ...
}
```

パスワードをハッシュ化するアルゴリズムとして、「SHA-256」と「BCrypt」が用意されています。 これらは同じ`PasswordEncoder`インタフェースで実装されているため、
以下のように`@Autowired`でインジェクションしようとすると、

``` java
@Component
public class UserServiceImpl implements UserService {
    @Autowired
    PasswordEncoder passwordEncoder;
    // ...
}
```

`NoUniqueBeanDefinitionException`が発生します。

``` console
com.example.UserServiceImpl.passwordEncoder; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.example.PasswordEncoder] is defined: expected single matching bean but found 2: bcryptPasswordEncoder,sha256PasswordEncoder
```

候補が複数あるため、使用する際は使用したいBeanの名前を明示しなくてはいけません。 SHA-256を使用したい場合は、以下のように`@Qualifier`に`sha256PasswordEncoder`を指定してください。

``` java
@Component
public class UserServiceImpl implements UserService {
    @Autowired
    @Qualifier("sha256PasswordEncoder")
    PasswordEncoder passwordEncoder;
    // ...
}
```

これで、`Sha256PasswordEncoder`がインジェクションされるようになります。

また、JavaConfigでのBean定義`@org.springframework.context.annotation.Primary`アノテーションをつけると`@Qualifier`で 修飾しなかった場合に使用されるBeanを指定できます。

次の例をみましょう。

``` java
@Configuration
@ComponentScan
public class AppConfig {

    @Bean
    PasswordEncoder sha256PasswordEncoder() {
        return new Sha256PasswordEncoder();
    }

    @Bean
    @Primary
    PasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
```

この場合は、次のように`@Qualifier`を指定しない場合は`BCryptPasswordEncoder`がインジェクションされます。

``` java
@Autowired
PasswordEncoder passwordEncoder;
```

次のように`@Qualifier`でBean名に`sha256PasswordEncoder`を指定した場合は`Sha256PasswordEncoder`がインジェクションされます。

``` java
@Autowired
@Qualifier("sha256PasswordEncoder")
PasswordEncoder passwordEncoder;
```

ただし、`@Qualifier`で指定する名前に実装の名前が含まれるのは好ましくありません。 DIによって疎結合したにも関わらず、呼び出し側で実装を特定してしまうとDIの意味がありません。ハリウッドの原則("Don't call us, we'll call you.")に明らかに反していますね。
文字列で実装を特定している分、DIを使わない場合よりも悪い状況とも言えます。


このような場合、Bean名を"実装名"ではなく"用途名"にするのが良いです。先の例では、 「デフォルトでは強力なBCryptアルゴリズムを使いたいが、軽量なSHA-256アルゴリズムも用意したい」 とします。この場合、次のようのように`Sha256PasswordEncoder`のBean名に用途を示す`lightweight`を指定するのがより良いです。

``` java
@Configuration
@ComponentScan
public class AppConfig {

    @Bean(name = "lightweight")
    PasswordEncoder sha256PasswordEncoder() {
        return new Sha256PasswordEncoder();
    }

    @Bean
    @Primary
    PasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
```

"軽量なアルゴリズム"を使用した実装をインジェクションしたい場合は、 次のように`@Qualifier`を指定すれば良いです。

``` java
@Autowired
@Qualifier("lightweight")
PasswordEncoder passwordEncoder;
```

より良い方法として、"用途"は文字列ではなく型(アノテーション)で表現することもできます。

次のように`@Qualifier`を付与した`@Lightweight`アノテーションを作成しましょう。

``` java
import org.springframework.beans.factory.annotation.Qualifier;

import java.lang.annotation.*;

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Qualifier
public @interface Lightweight {
}
```

Java Configで"軽量な"アルゴリズムに対応する実装の定義に`@Lightweight`アノテーションを付与してください。

``` java
@Configuration
@ComponentScan
public class AppConfig {

    @Bean
    @Lightweight
    PasswordEncoder sha256PasswordEncoder() {
        return new Sha256PasswordEncoder();
    }

    @Bean
    @Primary
    PasswordEncoder bcryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

    // ...
}
```

インジェクションする際も`@Lightweight`を付与すれば良いです。

``` java
@Autowired
@Lightweight
PasswordEncoder passwordEncoder;
```

型安全であるため、この方法がBean定義の重複に対する最も良い方法だと考えられます。 もちろん`@Sha256`のように実装を直接示すアノテーションを作るのは良くありません。

Spring Cloudで`RestTemplate`にクライアントロードバランサを含めるかどうかの判断にも[この方法が利用されています](https://github.com/spring-cloud/spring-cloud-commons/blob/master/docs/src/main/asciidoc/spring-cloud-commons.adoc#spring-resttemplate-as-a-load-balancer-client)。

``` java
@Autowired
@LoadBalanced // ロードバランサ有り
RestTeamplate restTemplate;
```

実装にはRibbonが使われているのですが、修飾名には実装名を含めないのがポイントです。
