LoginSignup
14
12

More than 5 years have passed since last update.

Springの@AutowiredにBy Nameによるfallbackの仕組みがあった件・・・

Last updated at Posted at 2017-01-21

直近の5年間・・・Spring Frameworkにどっぷり浸かった生活をしていたのですが、先日ソースコードレビューをしている時に初めて@AutowiredにBy Nameによるfallbackの仕組みがあったことを知りました・・・ :sweat_smile:

Note:
@Autowiredは、By Type(型の一致)によってインジェクション対象のBeanを解決することを指示するためのアノテーションで、同じ型のBeanが複数存在すると、どのBeanをインジェクションしてよいか判断できずエラーになります(@Primary@Order@Priorityが付与されたBeanがいると、そいつがインジェクションされる仕組みはありますが・・・)。
上記の説明は概ね間違ってはいないのですが、インジェクション候補が複数見つかった場合に、インジェクションするBeanをBy Nameで絞り込む仕組みがあったのです・・・・(いや〜知らなかった・・・=できないと思い込んでいた・・・)

@AutowiredのBy Nameによるfallbackを体験してみる

私がソースコードレビューしたのは非Spring BootのレガシーなSpringアプリケーションですが、Spring Bootで表現すると以下のようなソースコードになります。

ポイントは以下の2点です。

  • 同じ型のBeanを2つ以上定義する(+ @Primaryを付与したBeanを定義しない)
  • @Autowiredをつけたコンストラクタの引数の名前にfallback時に使うbean名を指定する
package com.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class DemoApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    private final RestTemplate restTemplate;

    @Autowired // Spring 4.3以上なら省略可能
    public DemoApplication(RestTemplate resourceRestTemplate) {
        this.restTemplate = resourceRestTemplate;
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println(restTemplate.getForObject("http://kazuki43zoo.com", String.class));
    }

    @Configuration
    static class Config {
        @Bean
        RestTemplate resourceRestTemplate() {
            return new RestTemplate(){
                // 動作検証用にメソッドコールを確認するためのデバッグコードを追加
                @Override
                public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)
                        throws RestClientException {
                    System.out.println("resourceRestTemplate called."); // デバッグコード
                    return super.getForObject(url, responseType, uriVariables);
                }
            };
        }
        @Bean
        RestTemplate authRestTemplate() {
            return new RestTemplate(){
                // 動作検証用にメソッドコールを確認するためのデバッグコードを追加
                @Override
                public <T> T getForObject(String url, Class<T> responseType, Object... uriVariables)
                        throws RestClientException {
                    System.out.println("authRestTemplate called.");
                    return super.getForObject(url, responseType, uriVariables); // デバッグコード
                }
            };
        }
    }

}

このクラスを実行すると・・・・

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.4.3.RELEASE)

2017-01-21 11:34:43.683  INFO 18469 --- [           main] com.example.DemoApplication              : Starting DemoApplication on Kazuki-no-MacBook-Pro.local with PID 18469 (/Users/xxx/Documents/workspace-sts-3.8.3.RELEASE/demo/target/classes started by shimizukazuki in /Users/xxx/Documents/workspace-sts-3.8.3.RELEASE/demo)
2017-01-21 11:34:43.688  INFO 18469 --- [           main] com.example.DemoApplication              : No active profile set, falling back to default profiles: default
2017-01-21 11:34:43.739  INFO 18469 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@61ca2dfa: startup date [Sat Jan 21 11:34:43 JST 2017]; root of context hierarchy
2017-01-21 11:34:44.120  WARN 18469 --- [           main] o.m.s.mapper.ClassPathMapperScanner      : No MyBatis mapper was found in '[com.example]' package. Please check your configuration.
2017-01-21 11:34:44.770  INFO 18469 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
resourceRestTemplate called.
<!DOCTYPE html>
<html>

  <head>
    <meta charset='utf-8' />
    <meta http-equiv="X-UA-Compatible" content="chrome=1" />
    <meta name="description" content="kazuki43zoo.github.io : " />

    <link rel="stylesheet" type="text/css" media="screen" href="stylesheets/stylesheet.css">

    <title>Introduce "Kazuki Shimizu"</title>
  </head>

  <!-- ... -->

</html>

2017-01-21 11:34:45.174  INFO 18469 --- [           main] com.example.DemoApplication              : Started DemoApplication in 1.95 seconds (JVM running for 2.242)
2017-01-21 11:34:45.174  INFO 18469 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@61ca2dfa: startup date [Sat Jan 21 11:34:43 JST 2017]; root of context hierarchy
2017-01-21 11:34:45.175  INFO 18469 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

コンソールに「resourceRestTemplate called.」とでているので、Bean名をresourceRestTemplateにしたBeanのメソッドが呼び出されたことが確認できます。

私は5年間・・・このケースはエラーになるものだと思い込んでました・・・:sob:

By Nameによるfallback仕様

型による解決(@Primary@Order@Priorityなどによる絞り込みも含む)でインジェクション候補が特定できない場合は、以下の名前を使って最終的な絞り込みを行います。

  • コンストラクタインジェクションの場合はコンストラクタの引数名 1
  • メソッドインジェクションの場合はメソッドの引数名 1
  • フィールドインジェクションの場合はフィールド名

インジェクション候補の中に一致する名前がない場合は、どのBeanをインジェクションしてよいか判断できずエラーになります。

まとめ

う〜ん・・・Springは奥(闇!?)が深い・・・:sweat_smile:
ちなみに・・・始めからBy NameによってインジェクションするBeanを解決したい場合は、@Autowiredではなく@javax.annotation.Resourceを使った方が効率的にインジェクションするBeanを見つけることができます。
ただし、@Resourceはコンストラクタに指定することができないのが残念なんだよな〜。
By TypeとBy Nameによるインジェクションにはそれぞれメリット・デメリットがあるわけですが・・・基本的にはBy Typeによるインジェクションを使うようにして、必要に応じてBy Nameによるインジェクションの採用を検討するのがいいのかな〜と思っています。

注釈


  1. 「デバッグ情報の出力(-g)」または「JDK 8で導入されたリフレクション用のパラメータ情報出力(-parameters)」のいずれかのコンパイルオプションの有効化が必要 

14
12
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
12