依存性の注入、あるいは依存性の逆転の例
依存性の注入について、簡単な例を考えてみた。
(1) 普通の場合
HeroクラスとWeaponクラスを例に考えてみる。
以下のようなクラスを考える。
Hero.java (charactorパッケージ)
package charactor;
import weapon.Sword;
import weapon.Weapon;
public class Hero {
private String name;
private int hp;
private Weapon weapon = new Sword();
public Hero() {
this("ミナト", 100);
}
public Hero(String name, int hp) {
this.name = name;
this.hp = hp;
}
public String getName() {
return name;
}
public int getHp() {
return hp;
}
public void useWeapon() {
System.out.print(name + "は ");
weapon.use();
}
}
Weapon.java (weaponパッケージ)
package weapon;
public interface Weapon {
void use();
}
Sword.java (weaponパッケージ)
package weapon;
public class Sword implements Weapon {
private String name;
public String getName() {
return name;
}
public Sword() {
this.name = "炎の剣";
}
@Override
public void use() {
System.out.println(name + "を使って切りつけた");
}
}
Main.java (mainパッケージ)
package main;
import charactor.Hero;
public class Main {
public static void main(String[] args) throws Exception {
Hero h = new Hero();
h.useWeapon();
}
}
依存性に着目すると、
(1) Mainクラスは、その中で Heroクラスのインスタンスを生成しているので、
「Heroクラスに依存している」と言える。
(2) Heroクラスは、その中で Swordクラスのインスタンスを生成しているので、
「Swordクラスに依存している」のだが、Weaponインターフェースを介在
させているので(ポリモーフィズム)、疎結合と言える。

(2) 依存性の注入(逆転)
まず、次の annotationを定義する。
Resource.java (annotationパッケージ)
package annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Resource {
Class value();
}
@Target(ElementType.FIELD) — このアノテーションがフィールド宣言の修飾子としてのみ記述できることを示す。
@Retention(RetentionPolicy.RUNTIME) — 注釈はコンパイラによってクラス・ファイルに記録され、実行時にVMによって保持されます。このため、リフレクト的に読み取ることができます。
(「列挙型RetentionPolicy」https://docs.oracle.com/javase/jp/8/docs/api/java/lang/annotation/RetentionPolicy.html#RUNTIME)
Class value() — この記述によって、アノテーション記述子 @Resource(引数) の「引数」に classリテラル を指定できる。
この @Resource 記述子を Heroクラスに記述する。
Hero.java (charactorパッケージ)
package charactor;
import annotation.Resource;
import weapon.Sword;
import weapon.Weapon;
public class Hero {
private String name;
private int hp;
@Resource(Sword.class) // ここにアノテーションを記述する
private Weapon weapon; // インスタンス生成は、はずす
public Hero() {
this("ミナト", 100);
}
public Hero(String name, int hp) {
this.name = name;
this.hp = hp;
}
public String getName() {
return name;
}
public int getHp() {
return hp;
}
public void useWeapon() {
weapon.use();
}
}
インスタンスを生成して、必要なクラスのフィールドにセットする役割のクラスを用意する。
Container.java (coreパッケージ)
package core;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import annotation.Resource;
public class Container {
public Object getInstance(Class clazz) throws Exception { (1)
Object obj = clazz.getConstructor().newInstance(); (2)
Field[] fields = clazz.getDeclaredFields(); (3)
for (Field f : fields) {
Annotation[] annotations = f.getAnnotations(); (4)
for (Annotation an : annotations) {
if (an instanceof Resource) { (5)
Resource resource = (Resource) an; (6)
Class target = resource.value(); (7)
f.setAccessible(true); (8)
f.set(obj, target.getConstructor().newInstance()); (9)
}
}
}
return obj;
}
}
(1) — 引数の Class clazz は、フィールドに @Resource をつけたクラス・リテラルを指定する。今回の場合は Hero.class。
(2) — 今回の場合、Heroクラス情報のコンストラクタの newInstance()メソッドで、Heroクラスのインスタンスを生成する。それを Object型に代入する。
(3) — クラスのフィールド情報を配列で取得している。今回の場合だと、Heroクラスには、String型のname、int型のhp、Weapon型のweapon の3つである。
(4) — それぞれのフィールドのアノテーションを配列で取得している。とは言っても、今回だとアノテーションは一つしかない。
(5) — 各アノテーションを調べて、そのアノテーションが Resourceならば、
(6) — Resourceアノテーションの引数に記述されたクラス・リテラルを取得して target とする。今回の場合は、Sword.class である。
(7) — 今から Hero.class のフィールド”weapon” に変更を加えるので、変更を可能にしている。
(8) — そのフィールドをもつオブジェクトについて、そのフィールドに targetのインスタンスをセットする。今回の場合は、Heroインスタンスの weaponフィールドに Sword.class のインスタンスを生成してセットしている。
Main.java (mainパッケージ)
package main;
import charactor.Hero;
import core.Container;
public class Main {
public static void main(String[] args) throws Exception {
Container con = new Container();
Hero h = (Hero) con.getInstance(Hero.class);
h.useWeapon();
}
}
Containerクラスの getInstance()メソッドに Hero.class リテラルを渡したら、Heroクラスの Weaponフィールドに @Resource で指定したクラス(Swordクラス) がセットされ、Object型になって返却される。それを Hero型にキャストしている。

カテゴリー: Java, memo, Spring
タグ: annotation, Container, Dependency Injection, Di, アノテーション, コンテナ, 依存性の注入, 依存性の逆転
カウント: 123