山崎屋の技術メモ

IT業界で働く中でテクノロジーを愛するSIerのシステムエンジニア👨‍💻 | AndroidとWebアプリの二刀流🧙‍♂️ | コードの裏にあるストーリーを綴るブログ執筆者✍️ | 日々進化するデジタル世界で学び続ける探究者🚀 | #TechLover #CodeArtisan、気になること、メモしておきたいことを書いていきます。

【Spring Framework】bean名による@Autowired

前回の記事で Spring Framework による簡単な DI を説明した。

yyama1556.hateblo.jp

これはプロパティの型を手掛かりに Spring が DI してくれていて、"byType" によるインジェクションという。

では、プロパティの型と同じクラスが2つ以上存在した場合はどちらをDIしてくれるのだろうか。

今回は bean の名前によるインジェクションである "byName" によるインジェクションを紹介する。

"byType" なのにプロパティの型と同じクラスが2つ以上存在した場合の挙動

フォルダ構成はこう。
f:id:yyama1556:20160810085417p:plain

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="
      http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context.xsd">
	<context:component-scan base-package="org.yyama.bean" />
</beans>

[org.yyama.bean]パッケージ配下のクラスをSpringコンテキストの管理対象としている。

Mainクラス

package org.yyama;

import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.yyama.bean.Fuga;

public class Main {
	public static void main(String... args) {
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
		Fuga fuga = ctx.getBean(Fuga.class);
		fuga.proc();
		ctx.close();
	}
}

Spring コンテナから Fuga のインスタンス fuga を取得し、fuga.proc() メソッドを実行している。

Fuga クラス

package org.yyama.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class Fuga {
	@Autowired()
	HogeInterface hoge;

	public void proc() {
		hoge.print();
	}
}

ここの hoge プロパティには HogeInterface をインプリメントした実装クラスがインジェクションされるのだが、今回はHogeInterface をインプリメントしたクラスが2つ存在する。その場合どのような挙動になるのかを実験する。

HogeInterfaceインターフェース

package org.yyama.bean;

public interface HogeInterface {
	public void print();
}

HogeImplAクラス

package org.yyama.bean;

import org.springframework.stereotype.Component;

@Component
public class HogeImplA implements HogeInterface {
	@Override
	public void print() {
		System.out.println("HogeImplAのprint()");
	}
}

print() メソッドで "HogeImplAのprint()" と出力している。

HogeImplBクラス

package org.yyama.bean;

import org.springframework.stereotype.Component;

@Component
public class HogeImplB implements HogeInterface {
	@Override
	public void print() {
		System.out.println("HogeImplBのprint()");
	}
}

print() メソッドで "HogeImplBのprint()" と出力している。

Mainクラスを実行すると・・・

エラーが出力された。簡単に訳すと「fugaの生成に失敗した。hogeフィールドの依存性が満たされない。[org.yyama.bean.HogeInterface]が定義されているが、これにひとつだけ一致することを期待していたが、2つ見つかった。hogeImplAとhogeImplBだ。」のように出力されている。

8 10, 2016 8:55:40 午前 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
情報: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4d405ef7: startup date [Wed Aug 10 08:55:40 JST 2016]; root of context hierarchy
8 10, 2016 8:55:40 午前 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
情報: Loading XML bean definitions from class path resource [applicationContext.xml]
8 10, 2016 8:55:41 午前 org.springframework.context.support.ClassPathXmlApplicationContext refresh
警告: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'fuga': Unsatisfied dependency expressed through field 'hoge': No qualifying bean of type [org.yyama.bean.HogeInterface] is defined: expected single matching bean but found 2: hogeImplA,hogeImplB; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.yyama.bean.HogeInterface] is defined: expected single matching bean but found 2: hogeImplA,hogeImplB
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'fuga': Unsatisfied dependency expressed through field 'hoge': No qualifying bean of type [org.yyama.bean.HogeInterface] is defined: expected single matching bean but found 2: hogeImplA,hogeImplB; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.yyama.bean.HogeInterface] is defined: expected single matching bean but found 2: hogeImplA,hogeImplB
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:569)
	at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:349)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1214)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:543)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:482)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:776)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:861)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:541)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
	at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
	at org.yyama.Main.main(Main.java:8)
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [org.yyama.bean.HogeInterface] is defined: expected single matching bean but found 2: hogeImplA,hogeImplB
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:172)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1065)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1019)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:566)
	... 15 more

byName によるインジェクションに修正する。

Fuga クラス、HogeImplA クラス、HogeImplB クラスを修正する。

Fuga クラス

package org.yyama.bean;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

@Component
public class Fuga {
	@Autowired()
	@Qualifier("hogeA")
	HogeInterface hoge;

	public void proc() {
		hoge.print();
	}
}

"@Autowired()" の下に、"@Qualifier("hogeA")" を追加した。これで [hogeA] という名前の bean をインジェクションするようSpring に要求している。

HogeImplAクラス

package org.yyama.bean;

import org.springframework.stereotype.Component;

@Component("hogeA")
public class HogeImplA implements HogeInterface {
	@Override
	public void print() {
		System.out.println("HogeImplAのprint()");
	}
}

"@Component" アノテーションに引数を追加している。これでこのクラスのインスタンスは "hogeA" という名前で Spring に管理される。

HogeImplB クラス

package org.yyama.bean;

import org.springframework.stereotype.Component;

@Component("hogeB")
public class HogeImplB implements HogeInterface {
	@Override
	public void print() {
		System.out.println("HogeImplBのprint()");
	}
}

もうお分かりだろうが、"@Component" アノテーションに引数を追加している。これでこのクラスのインスタンスは "hogeB" という名前で Spring に登録される。

これで "HogeInterface" をインプリメントした実装クラスは Spring コンテナ内に2つ存在するが、名前が異なるので見分けがつくということだ。

実行結果

8 10, 2016 9:11:18 午前 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
情報: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4d405ef7: startup date [Wed Aug 10 09:11:18 JST 2016]; root of context hierarchy
8 10, 2016 9:11:18 午前 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
情報: Loading XML bean definitions from class path resource [applicationContext.xml]
HogeImplAのprint()
8 10, 2016 9:11:18 午前 org.springframework.context.support.ClassPathXmlApplicationContext doClose
情報: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4d405ef7: startup date [Wed Aug 10 09:11:18 JST 2016]; root of context hierarchy

Fuga クラスで "@Qualifier("hogeA")" と指定しているので、Fuga クラスの hoge プロパティには "hogeA" という名前の bean がインジェクションされたことがわかる。

今日はここまで。


Spring 関連記事へのリンク集つくりました。


Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発

Spring徹底入門 Spring FrameworkによるJavaアプリケーション開発

[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ

[改訂新版]Spring入門 ――Javaフレームワーク・より良い設計とアーキテクチャ