Javaで簡単な自作のProxyタイプのAOPを作成

ここでは、SpringやJavaEE等のDIコンテナを使わずに、自作アノテーションを使って、 **ProxyタイプのAOP** を行う方法を紹介します。

ここで紹介するコードは、Javaの動的プロキシ（Proxy）を利用した **AOP（Aspect-OrientedProgramming）** の簡単な実装です。

+ `@LogExecution`というアノテーションを定義し、特定のメソッドに付与します
+ `AOPHandler`クラスでプロキシ（代理オブジェクト）を作成し、メソッド呼び出し時の前後でログを出力します
+ `Main`クラスで`ServiceImpl`クラスのプロキシを作成し、メソッド実行時にメッセージを出力します

# 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|LogExecution.java|カスタムアノテーション`@LogExecution`、AOPの対象メソッドを示すアノテーション|
|2|Service.java|AOPを適用する対象のインターフェース|
|3|ServiceImpl.java|実際に処理を実装するクラス、`@LogExecution`を付与してAOP対象とする|
|4|AOPHandler.java|動的プロキシを作成し、AOPの処理を行う|
|5|Main.java|動作確認|


# 3. 簡単なProxyタイプの自作AOPを作成

### 3.1. 自作アノテーションを作成

まず、AOPの対象であることを示す`@LogExecution`アノテーションを作成します。

```java:LogExecution.java
package proxyaop;

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.METHOD)         // メソッドに適用可能
public @interface LogExecution{
}

```

+ `@LogExecution`というカスタムアノテーションを定義します
+ `RetentionPolicy.RUNTIME`を指定し、実行時にリフレクションで取得できるようにします
+ `ElementType.METHOD`を指定し、メソッドにのみ適用可能させるようにします

### 3.2. インターフェースを作成

次にインターフェースを作成します。

```java:Service.java
package proxyaop;

public interface Service {
	void execute();
}
```

+ `Service`インターフェースを定義します
+ `execute()`メソッドを定義し、実装クラスで処理を記述させます



### 3.3. 実装クラスを作成

次に実装クラスを作成します。


```java:ServiceImpl.java
package proxyaop;

public class ServiceImpl implements Service {
    @Override
    @LogExecution
    public void execute() {
        System.out.println("Service is executing...");
    }
}
```

+ `Service`インターフェースを実装したクラスです
+ `execute()`メソッドで「Service is executing...」のメッセージを出力します
+ `execute()`メソッドに`@LogExecution`を付与してAOPの対象とします


### 3.4. プロキシクラス/ AOP の本体
次に、`@LogExecution`を利用してメソッドの実行前後に処理を追加するクラスを作成します。

```java:AOPHandler.java
package proxyaop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class AOPHandler implements InvocationHandler {

	private final Object target;
	
	public AOPHandler(Object target) {
        this.target = target;
    }
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 実装クラスのメソッドを取得
		Method implMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
		
		// メソッドに @LogExecution アノテーションが付いているかチェック
        if (implMethod.isAnnotationPresent(LogExecution.class)) {
            // メソッド実行前のログ出力
        	System.out.println("[LOG] Method " + method.getName() + " is starting...");
            // メソッドを実行
        	Object result = method.invoke(target, args);
            // メソッド実行後のログ出力
            System.out.println("[LOG] Method " + method.getName() + " has finished.");
            return result;
        } else {
        	// メソッドを実行
            return method.invoke(target, args);
        }
	}
	
	@SuppressWarnings("unchecked")	// 型キャストの警告を抑制
    public static <T> T createProxy(T target, Class<T> interfaceType) {
        return (T) Proxy.newProxyInstance(	// 動的プロキシを作成（実行時にインターフェースを実装）
            interfaceType.getClassLoader(),	// クラスローダーを設定（プロキシクラスのロードに必要）
            new Class<?>[]{interfaceType},	// 対象のインターフェースを指定
            new AOPHandler(target)			// メソッド呼び出しをフックし、AOPを適用
        );
    }
}
```

###### 3.4.1 invoke()メソッド

+ `@LogExecution`が付いているかを確認します
+ `@LogExecution`が付いている場合は、メソッド呼び出しの前後でログを出力します
+ `@LogExecution`が付いていない場合は、そのままメソッドを呼び出します

###### 3.4.2 createProxy()メソッド

+ JDKの動的プロキシ（Proxyクラス）を使って、AOPの対象となるオブジェクトをラップします
+ InvocationHandlerを通じて、メソッド呼び出しをフックします

### 3.5. 動作確認のためのMainクラスを作成

最後に、動作確認用の`Main`クラスを作成します。

```java:Main.java
package proxyaop;

public class Main {
    public static void main(String[] args) {
    	// ServiceImplのインスタンスを作成
    	// （ServiceImplはServiceインターフェースを実装している）
        Service service = new ServiceImpl();
        // createProxyメソッドを呼び出して、AOP用の動的プロキシを作成
        Service proxyService = AOPHandler.createProxy(service, Service.class);	
        // プロキシ経由でexecute()を呼び出す
        // AOPHandler（InvocationHandler）のinvoke()が実行される
        proxyService.execute();	
    }
}
```

+ `ServiceImpl`のインスタンスを作成します
+ `AOPHandler.createProxy()`を使用して、`Service`インターフェースのプロキシオブジェクトを作成します
+ プロキシ経由で`execute()`を呼び出し、`AOPHandler`の`invoke()`メソッドが実行させます

# 4. 実行結果
`Main.java`を実行すると、期待通りに`@LogExecution`が付与された`ServiceImpl`クラスの`execute`メソッドの前後で、ログが出力されることを確認できました。

```console:実行結果
C:\pleiades\2024-12\workspace\git\java\bin>C:\pleiades\2024-12\java\21\bin\java.exe proxyaop.Main
[LOG] Method execute is starting...
Service is executing...
[LOG] Method execute has finished.

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

---

以上