ここでは、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
アノテーションを作成します。
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. インターフェースを作成
次にインターフェースを作成します。
Service.java
package proxyaop;
public interface Service {
void execute();
}
-
Service
インターフェースを定義します -
execute()
メソッドを定義し、実装クラスで処理を記述させます
3.3. 実装クラスを作成
次に実装クラスを作成します。
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
を利用してメソッドの実行前後に処理を追加するクラスを作成します。
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
クラスを作成します。
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
メソッドの前後で、ログが出力されることを確認できました。
実行結果
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>
以上