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

直近の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名を指定する

```java
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のメソッドが呼び出されたことが確認できます。

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

# By Nameによるfallback仕様

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

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

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


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


# まとめ

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

# 注釈