DIとは?
- Dependency Injection - 依存性注入のこと
- クラス間の依存関係を外部から注入することで、開発者側でコンストラクタによるインスタンス生成が不要になる
-
new Hoge()
をせずともHogeクラスのインスタンスを利用できる
-
- Spring Bootだと以下3パターンがある
- コンストラクタインジェクション
- フィールドインジェクション
- セッターインジェクション
- ベストプラクティスはコンストラクタインジェクション
DIコンテナとは?
- DIを実施するコンテナのこと
- プロジェクト内のクラス間の依存関係を全て把握し、よしなにDIしてくれて、かつその処理を開発者の見えないところでひっそりやってくれる
- 汚れ仕事を全て引き受けてくれる黒子みたいな存在
作ったもの
自分がDIコンテナのことを理解するために作ったものなので、作業記録みたいなものと思ってもらえれば。
実際のアプリ開発で利用できるクオリティには程遠いので、Spring Bootが用意してるDIコンテナを使いましょう。
ソースコード解説
コアの部分はConstructorInjectionContainer.java↓
package di;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class ConstructorInjectionContainer {
static Map<String, Constructor<?>> constructorMap = new HashMap<>();
public static void register(Class<?> clazz) {
Stream.of(clazz.getConstructors())
.filter(constructor -> constructor.getParameterCount() > 0) //デフォルトコンストラクタを除外
.forEach(nullableConstructor -> {
constructorMap.put(clazz.getName(), nullableConstructor);
});
}
public static Object getInstance(Class<?> clazz) {
try {
return createInstance(clazz);
} catch (Exception e) {
log.warn("Failed to getInstance. message={}, cause={}", e.getMessage(), e.getCause());
return null;
}
}
@SuppressWarnings("unchecked")
private static <T> T createInstance(Class<T> clazz) {
try {
var constructor = constructorMap.get(clazz.getName()) != null ? constructorMap.get(clazz.getName()) : clazz.getDeclaredConstructor();
return (T) constructor
.newInstance(Stream.of(constructor.getParameterTypes())
.map(parameterType -> getInstance(parameterType))
.toArray()
);
} catch (Exception e) {
log.warn("Failed to createInstance", e);
return null;
}
}
}
動作確認時はこれをMainから呼び出す
import di.ConstructorInjectionContainer;
import di.FieldInjectionContainer;
import module.constructor.ClassD;
import module.constructor.ClassE;
import module.constructor.ClassF;
import module.field.ClassA;
public class Main {
public static void main(String[] args) {
ConstructorInjectionContainer.register(ClassD.class);
ConstructorInjectionContainer.register(ClassE.class);
ConstructorInjectionContainer.register(ClassF.class);
var classD = (ClassD) ConstructorInjectionContainer.getInstance(ClassD.class);
classD.print();
}
}
①registerでコンストラクタを登録
public static void register(Class<?> clazz) {
Stream.of(clazz.getConstructors())
.filter(constructor -> constructor.getParameterCount() > 0) //デフォルトコンストラクタを除外
.forEach(nullableConstructor -> {
constructorMap.put(clazz.getName(), nullableConstructor);
});
}
まずはMap<String, Constructor<?>> constructorMap
というMapに{クラス名, コンストラクタ}の組を登録していく。
あるクラスのインスタンス生成にどのコンストラクタが必要かをMapで管理する。
②getInstance -> createInstanceをコールして、インスタンスを取得する
インスタンス取得はgetInstance
がやる。
public static Object getInstance(Class<?> clazz) {
try {
return createInstance(clazz);
} catch (Exception e) {
log.warn("Failed to getInstance.", e);
return null;
}
}
といってもgetInstance
はcreateInstance
を読んでいるだけで、ロジックのコアはcreateInstance
の中にある。
@SuppressWarnings("unchecked")
private static <T> T createInstance(Class<T> clazz) {
try {
var constructor = constructorMap.get(clazz.getName()) != null ? constructorMap.get(clazz.getName()) : clazz.getDeclaredConstructor();
return (T) constructor
.newInstance(Stream.of(constructor.getParameterTypes())
.map(parameterType -> getInstance(parameterType))
.toArray()
);
} catch (Exception e) {
log.warn("Failed to createInstance", e);
return null;
}
}
まずはインスタンス生成したいクラスが引数のclazz
に入っているので、これをkeyにしてconstructorMap
からコンストラクタを特定する。
Mapにコンストラクタがなければ、getDeclaredConstructor()
を返すことにしている。
var constructor = constructorMap.get(clazz.getName()) != null ? constructorMap.get(clazz.getName()) : clazz.getDeclaredConstructor();
得られたコンストラクタからnewInstance
メソッドをコールして、インスタンスを生成する。
ただし、コンストラクタの引数はクラスによって異なるので柔軟に対応しないといけない。
とりあえずconstructor.getParameterTypes()
で引数のリストを取ってきてStreamにする。
Stream.of(constructor.getParameterTypes())
得られたparameterTypeから型情報(java.lang.Class)を取得してgetInstance
に再帰的に渡す。
ここが一番のミソ。
.map(parameterType -> getInstance(parameterType))
コンストラクタ引数の数だけgetInstance
が呼ばれ、最初にメソッドコールされたときのclazz
に依存するクラス達のインスタンスが芋づる式に取得できる。
参考