Springでの制御の反転と依存性注入の概要
1. 概要
この記事では、IoC(制御の反転)とDI(依存性注入)の概念を紹介し、次にこれらがSpringフレームワークでどのように実装されているかを見ていきます。
参考文献:
春の配線:@ Autowired、@ Resource、@ Inject
この記事では、依存性注入に関連するアノテーション、つまり@ Resource、@ Inject、@ Autowiredアノテーションの使用を比較対照します。
@Component vs @Repositoryおよび@Service in Spring
@Componentアノテーション、@ Repositoryアノテーション、および@Serviceアノテーションの違いと、それらを使用するタイミングについて説明します。
2. 制御の反転とは何ですか?
制御の反転は、プログラムのオブジェクトまたは部分の制御がコンテナまたはフレームワークに転送されるソフトウェアエンジニアリングの原則です。 これは、オブジェクト指向プログラミングのコンテキストで最もよく使用されます。
カスタムコードがライブラリを呼び出す従来のプログラミングとは対照的に、IoCでは、フレームワークがプログラムのフローを制御してカスタムコードを呼び出すことができます。 これを可能にするために、フレームワークは、追加の動作が組み込まれた抽象化を使用します。 If we want to add our own behavior, we need to extend the classes of the framework or plugin our own classes.
このアーキテクチャの利点は次のとおりです。
-
実装からタスクの実行を切り離す
-
異なる実装を簡単に切り替えることができます
-
プログラムのモジュール性の向上
-
コンポーネントを分離するか、その依存関係をモックし、コンポーネントがコントラクトを介して通信できるようにすることで、プログラムのテストがさらに容易になります。
制御の反転は、戦略設計パターン、サービスロケーターパターン、ファクトリパターン、依存性注入(DI)などのさまざまなメカニズムを通じて実現できます。
次にDIについて見ていきます。
3. 依存性注入とは何ですか?
依存性注入は、IoCを実装するためのパターンであり、反転されるコントロールはオブジェクトの依存性の設定です。
オブジェクトを他のオブジェクトに接続したり、オブジェクトを他のオブジェクトに「注入」する動作は、オブジェクト自体ではなくアセンブラーによって実行されます。
従来のプログラミングでオブジェクトの依存関係を作成する方法は次のとおりです。
public class Store {
private Item item;
public Store() {
item = new ItemImpl1();
}
}
上記の例では、Storeクラス自体の中にItemインターフェースの実装をインスタンス化する必要があります。
DIを使用することで、必要なItemの実装を指定せずに例を書き直すことができます。
public class Store {
private Item item;
public Store(Item item) {
this.item = item;
}
}
次のセクションでは、メタデータを介してItemの実装を提供する方法を説明します。
IoCとDIはどちらも単純な概念ですが、システムの構造に深い影響を与えるため、十分に理解する価値があります。
4. SpringIoCコンテナ
IoCコンテナーは、IoCを実装するフレームワークの共通の特性です。
Springフレームワークでは、IoCコンテナーはインターフェースApplicationContextで表されます。 Springコンテナは、beansと呼ばれるオブジェクトのインスタンス化、構成、およびアセンブル、およびそれらのライフサイクルの管理を担当します。
Springフレームワークは、ApplicationContextインターフェースのいくつかの実装を提供します—スタンドアロンアプリケーションの場合はClassPathXmlApplicationContextとFileSystemXmlApplicationContext、Webアプリケーションの場合はWebApplicationContext。
Beanをアセンブルするために、コンテナは構成メタデータを使用します。これは、XML構成または注釈の形式にすることができます。
コンテナを手動でインスタンス化する1つの方法は次のとおりです。
ApplicationContext context
= new ClassPathXmlApplicationContext("applicationContext.xml");
上記の例でitem属性を設定するには、メタデータを使用できます。 次に、コンテナはこのメタデータを読み取り、それを使用して実行時にBeanをアセンブルします。
Springでの依存性注入は、コンストラクター、セッター、またはフィールドを介して実行できます。
5. コンストラクターベースの依存性注入
constructor-based dependency injectionの場合、コンテナーは、設定する依存関係をそれぞれ表す引数を使用してコンストラクターを呼び出します。
Springは、主にタイプごとに各引数を解決し、その後に属性の名前と曖昧さ回避のためのインデックスを付けます。 アノテーションを使用して、Beanの構成とその依存関係を見てみましょう。
@Configuration
public class AppConfig {
@Bean
public Item item1() {
return new ItemImpl1();
}
@Bean
public Store store() {
return new Store(item1());
}
}
@Configurationアノテーションは、クラスがBean定義のソースであることを示します。 また、複数の構成クラスに追加することもできます。
@Beanアノテーションは、Beanを定義するメソッドで使用されます。 カスタム名を指定しない場合、Bean名はデフォルトでメソッド名になります。
デフォルトのsingletonスコープを持つBeanの場合、Springは最初にBeanのキャッシュされたインスタンスがすでに存在するかどうかを確認し、存在しない場合にのみ新しいインスタンスを作成します。 prototypeスコープを使用している場合、コンテナはメソッド呼び出しごとに新しいBeanインスタンスを返します。
Beanの構成を作成する別の方法は、XML構成を使用することです。
6. セッターベースの依存性注入
セッターベースのDIの場合、コンテナは、引数なしのコンストラクターまたは引数なしの静的ファクトリーメソッドを呼び出してBeanをインスタンス化した後、クラスのセッターメソッドを呼び出します。 アノテーションを使用してこの構成を作成しましょう。
@Bean
public Store store() {
Store store = new Store();
store.setItem(item1());
return store;
}
Beanの同じ構成にXMLを使用することもできます。
コンストラクターベースとセッターベースのタイプの注入を同じBeanに組み合わせることができます。 Springのドキュメントでは、必須の依存関係にはコンストラクターベースの注入を使用し、オプションの依存関係にはセッターベースの注入を使用することを推奨しています。
7. フィールドベース 依存性注入
フィールドベースのDIの場合、@Autowiredアノテーションで依存関係をマークすることにより、依存関係を挿入できます。
public class Store {
@Autowired
private Item item;
}
Storeオブジェクトの構築中に、Item Beanを注入するコンストラクターまたはセッターメソッドがない場合、コンテナーはリフレクションを使用してItemをStoreに注入します。
XML configurationを使用してこれを実現することもできます。
このアプローチはよりシンプルできれいに見えるかもしれませんが、次のようないくつかの欠点があるため、使用することは推奨されません。
-
このメソッドは、リフレクションを使用して依存関係を注入します。これは、コンストラクターベースまたはセッターベースの注入よりもコストがかかります
-
このアプローチを使用して、複数の依存関係を追加し続けるのは本当に簡単です。 複数の引数を持つコンストラクターインジェクションを使用している場合、クラスは単一の責任原則に違反する可能性があることを複数行うと考えられます。
@Autowiredアノテーションの詳細については、Wiring In Springの記事を参照してください。
8. 自動配線の依存関係
Wiringを使用すると、Springコンテナーは、定義されているBeanを検査することにより、コラボレーションするBean間の依存関係を自動的に解決できます。
XML構成を使用してBeanを自動配線する4つのモードがあります。
-
no:はデフォルト値です。これは、Beanに自動配線が使用されていないことを意味し、依存関係に明示的に名前を付ける必要があります。
-
byName:の自動配線はプロパティの名前に基づいて行われるため、Springは設定が必要なプロパティと同じ名前のBeanを探します。
-
byType:は、byNameの自動配線に似ていますが、プロパティのタイプのみに基づいています。 これは、Springが設定するプロパティと同じタイプのBeanを探すことを意味します。 そのタイプのBeanが複数ある場合、フレームワークは例外をスローします。
-
constructor:の自動配線はコンストラクター引数に基づいて行われます。つまり、Springはコンストラクター引数と同じタイプのBeanを検索します。
たとえば、上記で定義したitem1 BeanをstoreBeanにタイプ別に自動配線してみましょう。
@Bean(autowire = Autowire.BY_TYPE)
public class Store {
private Item item;
public setItem(Item item){
this.item = item;
}
}
タイプ別の自動配線に@Autowiredアノテーションを使用してBeanを注入することもできます。
public class Store {
@Autowired
private Item item;
}
同じタイプのBeanが複数ある場合は、@Qualifierアノテーションを使用してBeanを名前で参照できます。
public class Store {
@Autowired
@Qualifier("item1")
private Item item;
}
それでは、XML構成を使用してBeanをタイプ別に自動配線してみましょう。
次に、itemという名前のBeanを、XMLを介して名前でstore Beanのitemプロパティに挿入しましょう。
また、コンストラクターの引数またはセッターを使用して依存関係を明示的に定義することにより、自動配線をオーバーライドすることもできます。
9. 怠惰な初期化されたBean
デフォルトでは、コンテナは初期化中にすべてのシングルトンBeanを作成および設定します。 これを回避するには、Bean構成でlazy-init属性を値trueとともに使用できます。
結果として、item1 Beanは最初に要求されたときにのみ初期化され、起動時には初期化されません。 これの利点は初期化時間が短縮されることですが、トレードオフは、Beanが要求された後にのみ構成エラーが検出される可能性があることです。
10. 結論
この記事では、制御の反転と依存性注入の概念を示し、Springフレームワークでそれらを例示しました。
これらの概念の詳細については、MartinFowlerの記事をご覧ください。
また、Spring Framework Reference DocumentationでIoCとDIのSpring実装について詳しく知ることができます。