きり丸の技術日記

技術・エンジニアのイベント・資格等はこちらにまとめる予定です

SpringBootで同クラス別インスタンスをインジェクションできるようにする(BeanとQualifier)

きり丸アドベントカレンダー2020の24記事目です。


23日目の記事にて、RestOperationsインターフェースを使用して別システムのAPIを実行できるようにしました。23日目の状態では他システムのAPIを実行する直前にてインスタンスを生成していましたが、別の箇所でも使えるように生成ロジック自体を共通的にできそうです。

また、各外部連携用のクライアントで生成せずに、すでに生成された状態でインジェクションできると生成ロジックが流出しないし、記載量も減るのでいいことづくめです。

ただし、AシステムとBシステムのDI対象のRestOperationsインターフェースは同じものの、使用するコンポーネント側でURL等の各システムで特化している情報をもった異なる別のインスタンスをインジェクションさせる必要があります。

特に何の設定も行わないと、どちらのRestOperationsをインジェクションするべきかがわからないので、次のエラーログが出て起動できません。

Parameter 1 of constructor in com.example.demo.external.zipCloud.ZipCloudClientImpl required a single bean, but 2 were found:
        - dummyUserRestOperations: defined by method 'dummyUserRestOperations' in class path resource [com/example/demo/external/operation/RestOperationConfig.class]
        - zipCloudRestOperations: defined by method 'zipCloudRestOperations' in class path resource [com/example/demo/external/operation/RestOperationConfig.class]

今回の記事では、共通化して同クラスになったものの、目的別のインスタンスを使用するクラスで目的通りにインジェクションさせるようにします。

ゴール

  • 同クラス別インスタンスを目的どおりにインジェクションさせる
  • RestOperationsの生成ロジックを共通化する

23日目の記事では、住所コードから住所を取得するAPIを使用しました。

今回の記事では、ダミーユーザを生成してくれるAPIを使用します。

randomuser.me

環境

  • Java
    • 15
  • org.springframework.boot:spring-boot-starter-web
    • 2.4.0
  • Lombok

手順

RestOperations生成ロジックを共通化する


23日目の記事では、生成したロジックを別クラスに移しているだけです。

解説等は上の記事から見てください。

ファイル名:RestOperationFactory.java

@RequiredArgsConstructor
@Component
public class RestOperationFactory {
  private final RestTemplateBuilder restTemplateBuilder;

  public RestOperations createRestOperations(ExternalProperties props) {
    MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
    converter.setSupportedMediaTypes(List.of(MediaType.TEXT_PLAIN));

    RestTemplate build = restTemplateBuilder
        .setConnectTimeout(Duration.ofSeconds(props.getConnectionTimeout()))
        .setReadTimeout(Duration.ofSeconds(props.getReadTimeout()))
        .build();
    build.getMessageConverters().add(converter);

    return build;
  }
}

システムごとのRestOperationsインスタンスを生成する


クラスに@Configurationアノテーションを付与します。

メソッドごとに@Beanをつけると、メソッドごとにインジェクション対象のインスタンスになります。@Beanに何も指定しないと、メソッド名がそのまま識別名になります。

ファイル名:RestOperationConfig.java

@Configuration
@RequiredArgsConstructor
public class RestOperationConfig {
  private final RestOperationFactory restOperationFactory;

  @Bean
  RestOperations dummyUserRestOperations(DummyUserClientProperties props){
    return restOperationFactory.createRestOperations(props);
  }

  @Bean
  RestOperations zipCloudRestOperations(ZipCloudClientProperties props){
    return restOperationFactory.createRestOperations(props);
  }
}

インスタンスを識別しながらQualifierを使用してインジェクションする


インジェクションする際に@Qualifierを指定します。上でも説明しましたが、@Beanのパラメータを指定していればその値を、指定しなければメソッド名を入力してください。

ファイル名:ZipCloudClientImpl.java

  public ZipCloudClientImpl(
      ZipCloudClientProperties props,
      @Qualifier("zipCloudRestOperations") RestOperations restOperations
  ) {
    this.props = props;
    this.restOperations = restOperations;
  }

ソースコード

アドベントカレンダー24日目。
github.com

終わりに

やり方がわからないと、共通化に苦労してしまいます。または、生成ロジックだけは共通化したものの、各クラスでインスタンスを何度も生成する必要があります。

私はBeanQualifierの理解に苦しみました。

インジェクション対象で同クラス別インスタンスを扱うことはユースケースとしては少ないですが、覚えておくと地味に助かると思いますので、ぜひ覚えて下さい。


この記事がお役に立ちましたら、各種SNSでのシェアや、今後も情報発信しますのでフォローよろしくお願いします。

参考記事

敬称略

paiza開発日誌:面倒な手続き不要!「Web API」の超お手軽活用術をJavaScriptコード付きで一挙大公開! paiza.hatenablog.com

この記事に対して反応してくださった内容の方が正直濃いです。
私のブログで見たうえで、さらに良い設計をする場合はこちらの記事を参考にしてください。

blog.ik.am

類似記事

きり丸アドベントカレンダー2020 adventar.org

きり丸のHerokuページ
https://kirimaru-todoapp.herokuapp.com/

25日目のアドベントカレンダーの記事。この記事は振り返りです。 https://nainaistar.hatenablog.com/entry/2020/12/25/083000nainaistar.hatenablog.com

f:id:nainaistar:20201109133010p:plain