依存性注入

依存性注入(DI)は、コンストラクターの引数、ファクトリメソッドへの引数、オブジェクトインスタンスの構築後に設定されるプロパティを通じてのみ、オブジェクトが依存関係(つまり、動作する他のオブジェクト)を定義するプロセスです。ファクトリメソッドから返されます。コンテナーは、Bean を作成するときにそれらの依存関係を注入します。このプロセスは、基本的に、クラスの直接構築または Service Locator パターンを使用して、Bean 自体のインスタンス化または依存関係の位置を制御する Bean 自体の逆(つまり、Inversion of Control)です。

DI の原則によりコードは簡潔になり、オブジェクトに依存関係が提供されると、デカップリングがより効果的になります。オブジェクトは依存関係を検索せず、依存関係の場所またはクラスを知りません。その結果、特に依存関係がインターフェースまたは抽象基本クラスにある場合、クラスのテストが容易になり、ユニットテストでスタブまたはモックの実装を使用できるようになります。

DI は、コンストラクターベースの依存性注入setter ベースの依存性注入の 2 つの主要なバリアントに存在します。

コンストラクターベースの依存性注入

コンストラクターベースの DI は、それぞれが依存関係を表すいくつかの引数を使用してコンストラクターを呼び出すコンテナーによって実現されます。特定の引数を使用して static ファクトリメソッドを呼び出して Bean を構築することはほぼ同等であり、この説明ではコンストラクターと static ファクトリメソッドの引数を同様に扱います。次の例は、コンストラクターの注入でのみ依存関係を注入できるクラスを示しています。

  • Java

  • Kotlin

public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on a MovieFinder
	private final MovieFinder movieFinder;

	// a constructor so that the Spring container can inject a MovieFinder
	public SimpleMovieLister(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}
// a constructor so that the Spring container can inject a MovieFinder
class SimpleMovieLister(private val movieFinder: MovieFinder) {
	// business logic that actually uses the injected MovieFinder is omitted...
}

このクラスには特別なことは何もないことに注意してください。これは、コンテナー固有のインターフェース、基本クラス、アノテーションに依存しない POJO です。

コンストラクター引数解決

コンストラクターの引数解決の一致は、引数の型を使用して発生します。Bean 定義のコンストラクター引数に潜在的なあいまいさが存在しない場合、コンストラクター引数が Bean 定義で定義される順序は、Bean がインスタンス化されるときにそれらの引数が適切なコンストラクターに提供される順序です。次のクラスを検討してください。

  • Java

  • Kotlin

package x.y;

public class ThingOne {

	public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
		// ...
	}
}
package x.y

class ThingOne(thingTwo: ThingTwo, thingThree: ThingThree)

ThingTwo クラスと ThingThree クラスが継承によって関連付けられていないと仮定すると、潜在的なあいまいさは存在しません。次の構成は正常に機能し、<constructor-arg/> 要素でコンストラクター引数のインデックスまたは型を明示的に指定する必要はありません。

<beans>
	<bean id="beanOne" class="x.y.ThingOne">
		<constructor-arg ref="beanTwo"/>
		<constructor-arg ref="beanThree"/>
	</bean>

	<bean id="beanTwo" class="x.y.ThingTwo"/>

	<bean id="beanThree" class="x.y.ThingThree"/>
</beans>

別の Bean が参照される場合、型は既知であり、一致が発生する可能性があります(前の例の場合のように)。<value>true</value> などの単純な型が使用される場合、Spring は値の型を判別できないため、ヘルプなしでは型ごとに一致できません。次のクラスを検討してください。

  • Java

  • Kotlin

package examples;

public class ExampleBean {

	// Number of years to calculate the Ultimate Answer
	private final int years;

	// The Answer to Life, the Universe, and Everything
	private final String ultimateAnswer;

	public ExampleBean(int years, String ultimateAnswer) {
		this.years = years;
		this.ultimateAnswer = ultimateAnswer;
	}
}
package examples

class ExampleBean(
	private val years: Int, // Number of years to calculate the Ultimate Answer
	private val ultimateAnswer: String // The Answer to Life, the Universe, and Everything
)

コンストラクター引数型の一致

上記のシナリオでは、次の例に示すように、type 属性を使用してコンストラクター引数の型を明示的に指定すると、コンテナーは単純型との型マッチングを使用できます。

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg type="int" value="7500000"/>
	<constructor-arg type="java.lang.String" value="42"/>
</bean>

コンストラクター引数インデックス

次の例に示すように、index 属性を使用して、コンストラクター引数のインデックスを明示的に指定できます。

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg index="0" value="7500000"/>
	<constructor-arg index="1" value="42"/>
</bean>

複数の単純な値のあいまいさを解決することに加えて、インデックスを指定すると、コンストラクターに同じ型の 2 つの引数がある場合のあいまいさを解決できます。

インデックスは 0 ベースです。

コンストラクター引数名

次の例に示すように、コンストラクターパラメーター名を使用して値を明確にすることもできます。

<bean id="exampleBean" class="examples.ExampleBean">
	<constructor-arg name="years" value="7500000"/>
	<constructor-arg name="ultimateAnswer" value="42"/>
</bean>

これをすぐに動作させるには、Spring がコンストラクターからパラメーター名を検索できるように、-parameters フラグを有効にしてコードをコンパイルする必要があることに注意してください。-parameters フラグを使用してコードをコンパイルできない場合、またはコンパイルしたくない場合は、@ConstructorProperties (標準 Javadoc) JDK アノテーションを使用して、コンストラクター引数に明示的に名前を付けることができます。サンプルクラスは次のようになります。

  • Java

  • Kotlin

package examples;

public class ExampleBean {

	// Fields omitted

	@ConstructorProperties({"years", "ultimateAnswer"})
	public ExampleBean(int years, String ultimateAnswer) {
		this.years = years;
		this.ultimateAnswer = ultimateAnswer;
	}
}
package examples

class ExampleBean
@ConstructorProperties("years", "ultimateAnswer")
constructor(val years: Int, val ultimateAnswer: String)

setter ベースの依存性注入

setter ベースの DI は、引数なしのコンストラクターまたは引数なしの static ファクトリメソッドを呼び出して Bean をインスタンス化した後に、コンテナーが setter メソッドを Bean で呼び出すことによって実現されます。

次の例は、純粋な setter インジェクションを使用することによってのみ依存関係をインジェクトできるクラスを示しています。このクラスは従来の Java です。これは、コンテナー固有のインターフェース、基本クラス、アノテーションに依存しない POJO です。

  • Java

  • Kotlin

public class SimpleMovieLister {

	// the SimpleMovieLister has a dependency on the MovieFinder
	private MovieFinder movieFinder;

	// a setter method so that the Spring container can inject a MovieFinder
	public void setMovieFinder(MovieFinder movieFinder) {
		this.movieFinder = movieFinder;
	}

	// business logic that actually uses the injected MovieFinder is omitted...
}
class SimpleMovieLister {

	// a late-initialized property so that the Spring container can inject a MovieFinder
	lateinit var movieFinder: MovieFinder

	// business logic that actually uses the injected MovieFinder is omitted...
}

ApplicationContext は、管理する Bean のコンストラクターベースおよび setter ベースの DI をサポートします。また、コンストラクターアプローチによっていくつかの依存関係がすでに注入された後の setter ベースの DI もサポートします。依存関係は BeanDefinition の形式で構成します。これを PropertyEditor インスタンスと組み合わせて使用して、プロパティをある形式から別の形式に変換します。ただし、ほとんどの Spring ユーザーは、これらのクラスを直接(つまり、プログラムで)操作するのではなく、XML bean 定義、アノテーション付きコンポーネント(つまり、@Component@Controller などでアノテーションが付けられたクラス)、Java ベースの @Bean メソッドを操作します。@Configuration クラス。次に、これらのソースは内部で BeanDefinition のインスタンスに変換され、Spring IoC コンテナーインスタンス全体をロードするために使用されます。

コンストラクター vs setter、どちらの DI を選ぶ?

コンストラクターベースの DI と setter ベースの DI を混在させることができるため、必須の依存関係にはコンストラクターを使用し、オプションの依存関係には setter メソッドまたは構成メソッドを使用するのが良い経験則です。setter メソッドで @Autowired アノテーションを使用すると、プロパティを必須の依存関係にすることができることに注意してください。ただし、引数をプログラム的に検証するコンストラクターインジェクションを使用することをお勧めします。

Spring チームは通常、アプリケーションコンポーネントを不変オブジェクトとして実装し、必要な依存関係が null でないことを保証できるため、コンストラクターインジェクションを推奨しています。さらに、コンストラクターが注入したコンポーネントは常に、完全に初期化された状態でクライアント(呼び出し)コードに返されます。副次的な注意事項として、コンストラクター引数が多数あることはコードの悪臭であり、クラスの責任が多すぎる可能性があることを意味し、関心事の適切な分離に対処するためにリファクタリングする必要があります。

Setter インジェクションは、主に、クラス内で適切なデフォルト値を割り当てることができるオプションの依存関係にのみ使用する必要があります。それ以外の場合、コードが依存関係を使用するすべての場所で非 null チェックを実行する必要があります。setter インジェクションの利点の 1 つは、setter メソッドが、そのクラスのオブジェクトを後で再構成または再インジェクションしやすくすることです。JMX MBean による管理は、setter インジェクションの魅力的なユースケースです。

特定のクラスに最も意味のある DI スタイルを使用します。場合によっては、ソースがないサードパーティクラスを処理するときに、選択が行われます。例: サードパーティのクラスが setter メソッドを公開しない場合、コンストラクターインジェクションが DI の唯一の利用可能な形式である可能性があります。

依存関係解決プロセス

コンテナーは、次のように Bean 依存関係の解決を実行します。

  • ApplicationContext は、すべての Bean を記述する構成メタデータで作成および初期化されます。構成メタデータは、XML、Java コード、アノテーションによって指定できます。

  • 各 Bean の依存関係は、プロパティ、コンストラクター引数、静的ファクトリメソッドの引数の形式で表されます(通常のコンストラクターの代わりにそれを使用する場合)。これらの依存関係は、Bean が実際に作成されるときに Bean に提供されます。

  • 各プロパティまたはコンストラクターの引数は、設定する値の実際の定義、またはコンテナー内の別の Bean への参照です。

  • 値である各プロパティまたはコンストラクター引数は、指定された形式からそのプロパティまたはコンストラクター引数の実際の型に変換されます。デフォルトでは、Spring は、ストリング形式で提供された値を、intlongStringboolean などのすべての組み込み型に変換できます。

Spring コンテナーは、コンテナーの作成時に各 Bean の構成を検証します。ただし、Bean が実際に作成されるまで、Bean プロパティ自体は設定されません。シングルトンスコープで事前インスタンス化(デフォルト)に設定された Bean は、コンテナーの作成時に作成されます。スコープは Bean スコープで定義されています。それ以外の場合、Bean はリクエストされたときにのみ作成されます。Bean を作成すると、Bean の依存関係とその依存関係の依存関係(など)が作成および割り当てられるため、Bean のグラフが作成される可能性があります。これらの依存関係間の解決の不一致は、遅れて、つまり、影響を受ける Bean を最初に作成したときに表示されることに注意してください。

循環依存関係

主にコンストラクターインジェクションを使用する場合、解決できない循環依存シナリオを作成することができます。

次に例を示します: クラス A は、コンストラクターインジェクションを通じてクラス B のインスタンスを必要とし、クラス B は、コンストラクターインジェクションを通じてクラス A のインスタンスを必要とします。クラス A および B の Bean を相互に注入するように構成すると、Spring IoC コンテナーは実行時にこの循環参照を検出し、BeanCurrentlyInCreationException をスローします。

考えられる解決策の 1 つは、一部のクラスのソースコードを編集して、コンストラクターではなく setter で構成することです。または、コンストラクターインジェクションを避け、setter 注入のみを使用します。つまり、推奨されていませんが、setter インジェクションで循環依存関係を構成できます。

典型的な場合(循環依存関係なし)とは異なり、Bean A と Bean B の間の循環依存関係により、完全に初期化される前に、一方の Bean が他方に強制的に注入されます(従来の鶏と卵のシナリオ)。

一般的に Spring が正しいことをすることを信頼することができます。コンテナーのロード時に、存在しない Bean への参照や循環依存関係などの構成の課題を検出します。Spring は、Bean が実際に作成されたときに、プロパティを設定し、依存関係をできるだけ遅く解決します。つまり、正しくロードされた Spring コンテナーは、オブジェクトまたはその依存関係の 1 つを作成する際に問題が発生した場合に、オブジェクトをリクエストしたときに後で例外を生成できます。たとえば、Bean は、欠落または無効の結果として例外をスローします。プロパティ。いくつかの構成の課題のこの潜在的に遅延した可視性が、ApplicationContext 実装がデフォルトでシングルトン Bean を事前インスタンス化する理由です。これらの Bean を実際に必要になる前に作成するための事前の時間とメモリを犠牲にして、ApplicationContext の作成時に、後でではなく、構成の課題を発見します。シングルトン Bean が先行して事前インスタンス化されるのではなく、遅延して初期化されるように、このデフォルトの動作をオーバーライドすることもできます。

循環依存関係が存在しない場合、1 つ以上の連携 Bean が依存 Bean に注入されるときに、各連携 Bean は依存 Bean に注入される前に完全に構成されます。これは、Bean A が Bean B に依存している場合、Spring IoC コンテナーは、Bean A で setter メソッドを呼び出す前に Bean B を完全に構成することを意味します。つまり、Bean はインスタンス化されます (事前にインスタンス化されたシングルトンでない場合)。)、その依存関係が設定され、関連するライフサイクルメソッド ( 構成された init メソッドInitializingBean コールバックメソッドなど) が呼び出されます。

依存性注入の例

次の例では、setter ベースの DI に XML ベースの構成メタデータを使用しています。Spring XML 構成ファイルのごく一部は、次のようにいくつかの Bean 定義を指定しています。

<bean id="exampleBean" class="examples.ExampleBean">
	<!-- setter injection using the nested ref element -->
	<property name="beanOne">
		<ref bean="anotherExampleBean"/>
	</property>

	<!-- setter injection using the neater ref attribute -->
	<property name="beanTwo" ref="yetAnotherBean"/>
	<property name="integerProperty" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

次の例は、対応する ExampleBean クラスを示しています。

  • Java

  • Kotlin

public class ExampleBean {

	private AnotherBean beanOne;

	private YetAnotherBean beanTwo;

	private int i;

	public void setBeanOne(AnotherBean beanOne) {
		this.beanOne = beanOne;
	}

	public void setBeanTwo(YetAnotherBean beanTwo) {
		this.beanTwo = beanTwo;
	}

	public void setIntegerProperty(int i) {
		this.i = i;
	}
}
class ExampleBean {
	lateinit var beanOne: AnotherBean
	lateinit var beanTwo: YetAnotherBean
	var i: Int = 0
}

上記の例では、setter は XML ファイルで指定されたプロパティと一致するように宣言されています。次の例では、コンストラクターベースの DI を使用しています。

<bean id="exampleBean" class="examples.ExampleBean">
	<!-- constructor injection using the nested ref element -->
	<constructor-arg>
		<ref bean="anotherExampleBean"/>
	</constructor-arg>

	<!-- constructor injection using the neater ref attribute -->
	<constructor-arg ref="yetAnotherBean"/>

	<constructor-arg type="int" value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

次の例は、対応する ExampleBean クラスを示しています。

  • Java

  • Kotlin

public class ExampleBean {

	private AnotherBean beanOne;

	private YetAnotherBean beanTwo;

	private int i;

	public ExampleBean(
		AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
		this.beanOne = anotherBean;
		this.beanTwo = yetAnotherBean;
		this.i = i;
	}
}
class ExampleBean(
		private val beanOne: AnotherBean,
		private val beanTwo: YetAnotherBean,
		private val i: Int)

Bean 定義で指定されたコンストラクター引数は、ExampleBean のコンストラクターへの引数として使用されます。

ここで、コンストラクターを使用する代わりに、Spring が static ファクトリメソッドを呼び出してオブジェクトのインスタンスを返すように指示されている、この例のバリアントを考えます。

<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
	<constructor-arg ref="anotherExampleBean"/>
	<constructor-arg ref="yetAnotherBean"/>
	<constructor-arg value="1"/>
</bean>

<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>

次の例は、対応する ExampleBean クラスを示しています。

  • Java

  • Kotlin

public class ExampleBean {

	// a private constructor
	private ExampleBean(...) {
		...
	}

	// a static factory method; the arguments to this method can be
	// considered the dependencies of the bean that is returned,
	// regardless of how those arguments are actually used.
	public static ExampleBean createInstance (
		AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {

		ExampleBean eb = new ExampleBean (...);
		// some other operations...
		return eb;
	}
}
class ExampleBean private constructor() {
	companion object {
		// a static factory method; the arguments to this method can be
		// considered the dependencies of the bean that is returned,
		// regardless of how those arguments are actually used.
		@JvmStatic
		fun createInstance(anotherBean: AnotherBean, yetAnotherBean: YetAnotherBean, i: Int): ExampleBean {
			val eb = ExampleBean (...)
			// some other operations...
			return eb
		}
	}
}

static ファクトリメソッドへの引数は、コンストラクターが実際に使用された場合とまったく同じように、<constructor-arg/> 要素によって提供されます。ファクトリメソッドによって返されるクラスの型は、static ファクトリメソッドを含むクラスと同じ型である必要はありません(ただし、この例ではそうです)。インスタンス(非静的)ファクトリメソッドは(class 属性の代わりに factory-bean 属性を使用することを除いて)基本的に同じ方法で使用できるため、ここではそれらの詳細については説明しません。