Javaで簡単な自作DIを作成

ここでは、SpringやJavaEE等のDIコンテナを使わずに、自作アノテーションを使って **依存性注入（DI）** を行う方法を紹介します。
リフレクションを利用して、クラスのフィールドにアノテーションが付いているかをチェックし、自動でインスタンスを注入する仕組みを作ります。

# 1. 環境

+ java：openjdk version "21.0.5"
```
C:\pleiades\2024-12\java\21\bin>java -version
openjdk version "21.0.5" 2024-10-15 LTS
OpenJDK Runtime Environment Temurin-21.0.5+11 (build 21.0.5+11-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.5+11 (build 21.0.5+11-LTS, mixed mode, sharing)

C:\pleiades\2024-12\java\21\bin>
```

# 2. 作成するファイル
作成するファイルは以下となります。
|No　|ファイル名|説明|
|:---|:---|:---|
|1|MyAutowired.java|カスタムアノテーション`@MyAutowired`、DIを示す自作アノテーション|
|2|HelloService.java|DI対象のクラス|
|3|MyController.java|`@MyAutowired`を使ってDIを適用するクラス|
|4|MyDIContainer.java|自作のDIコンテナ|
|5|Main.java|動作確認|

# 3. 簡単な自作DIを作成
### 3.1. 自作アノテーションを作成
まず、DIの対象であることを示す`@MyAutowired`アノテーションを作成します。
```java:MyAutowired.java
package di;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)		// 実行時にアノテーションを保持
@Target(ElementType.FIELD)				// フィールドに適用
public @interface MyAutowired {
}
```

### 3.2. DI の対象となるクラス


依存関係としてインジェクトするクラスを作成します。
```java:HelloService.java
package di;

public class HelloService {
	public void sayHello() {
		System.out.println("Hello, Custom DI!");
	}
}
```
この`HelloService`クラスのインスタンスを、`@MyAutowired`の付いたフィールドに自動注入します。

### 3.3. 自作の DI コンテナ（MyDIContainer）を作成
次に、`@MyAutowired`をスキャンし、対象のクラスにインスタンスを注入する`MyDIContainer`クラスを作成します。

```java:MyDIContainer.java
package di;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class MyDIContainer {

	private Map<Class<?>, Object> beans = new HashMap<>();

    public MyDIContainer() {
        // 依存オブジェクトを手動で登録（実際のフレームワークでは自動スキャン）
        beans.put(HelloService.class, new HelloService());
    }

    public void injectDependencies(Object object) {
        Class<?> clazz = object.getClass();							// 渡されたオブジェクトのクラス情報を取得
        for (Field field : clazz.getDeclaredFields()) {				// クラスのフィールドを取得
            if (field.isAnnotationPresent(MyAutowired.class)) {		// フィールドに @MyAutowired があるかチェック
                Class<?> fieldType = field.getType();				// フィールドの型を取得
                Object dependency = beans.get(fieldType);			// 依存オブジェクトを beans から取得
                if (dependency != null) {
                    field.setAccessible(true);						// private フィールドでもアクセスできるようにする
                    try {
                        field.set(object, dependency);				// フィールドにインスタンスをセット
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException("Failed to inject dependency", e);
                    }
                }
            }
        }
    }
}
```
`injectDependencies()`メソッドは、オブジェクトのフィールドをスキャンし、`@MyAutowired`が付いたフィールドに適切なインスタンスを注入します。

### 3.4. `@MyAutowired`を使ってDIを適用するクラス
次に、`@MyAutowired`を利用して`HelloService`を DIするクラスを作成します。

```java:MyController.java
package di;

public class MyController {
	@MyAutowired
	private HelloService helloService;	// 自動 DI

	public void execute() {
		helloService.sayHello();		// HelloService のメソッドを実行
	}
}
```
通常の `@Autowired`のように、`@MyAutowired`で`HelloService`を自動DIできます。

### 3.5. 動作確認のための`Main`クラスを作成
最後に、`MyDIContainer`を使って`MyController`のインスタンスを作成し、依存関係を注入します。

```java:Main.java
package di;

public class Main {
	public static void main(String[] args) {
		MyDIContainer container = new MyDIContainer();		// DI コンテナを作成
		MyController controller = new MyController();	// コントローラを作成
		
		container.injectDependencies(controller);			// 依存関係を注入

		controller.execute(); 								// 実行
	}
}
```

# 4. 実行結果
`Main.java`を実行すると、期待通り、MyControllerにHelloServiceが自動的にDIされ、期待通りに動作することを確認できました。

```console:実行結果
C:\pleiades\2024-12\workspace\git\java\bin>C:\pleiades\2024-12\java\21\bin\java.exe di.Main
Hello, Custom DI!

C:\pleiades\2024-12\workspace\git\java\bin>
```


---

以上