読者です 読者をやめる 読者になる 読者になる

山崎屋の技術メモ

IT業界で働く中で、気になること、メモしておきたいことを書いていきます。

【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();
	}
}

特に難しいところはない。

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のproc()");
	}
}

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

HogeImplBクラス

package org.yyama.bean;

import org.springframework.stereotype.Component;

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

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

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のproc()");
	}
}

"@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のproc()");
	}
}

もうお分かりだろうが、"@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のproc()
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 関連記事へのリンク集つくりました。