【Java学習|豆知識】Javaの動的プロキシとアノテーションで実現する「横断的関心事」のスマートな分離

導入: なぜProxyが必要なのか?

現場で開発をしていると、「すべてのメソッドの実行時間を計測したい」「特定の処理の前後にログを自動で入れたい」といった要望をよく受けます。これをすべてのメソッドに手書きで実装すると、コードが重複し、保守性が著しく低下します。JavaのProxy.newProxyInstance()を活用すれば、ビジネスロジックを汚すことなく、これらの「横断的関心事」をスマートに注入できます。

基礎知識: ダイナミックプロキシとアノテーションの役割

Proxy.newProxyInstance()は、実行時にインターフェースを実装したクラスを動的に生成する仕組みです。これにリフレクションを組み合わせることで、メソッド呼び出しをインターセプト(横取り)できます。
また、アノテーション(@Retention(RetentionPolicy.RUNTIME)や@Target(ElementType.METHOD))を使うことで、「どのメソッドに処理を適用するか」というメタデータをコードに付与できます。これにより、柔軟な制御が可能になります。

実装/解決策

実装の流れは以下の通りです。
1. 適用対象を示すアノテーションを定義する。
2. InvocationHandlerインターフェースを実装し、メソッド呼び出しをフックする処理を書く。
3. Proxy.newProxyInstance()で対象クラスをラップする。

サンプルプログラム

import java.lang.annotation.;
import java.lang.reflect.;

// 1. 実行時間を計測したいメソッドに付けるアノテーション
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface LogExecutionTime {}

// インターフェース
interface Service {
@LogExecutionTime
void execute();
}

// 実装クラス
class ServiceImpl implements Service {
public void execute() {
System.out.println(“メインの処理を実行中…”);
}
}

// 2. 処理を横取りするハンドラ
class TimingHandler implements InvocationHandler {
private final Object target;

public TimingHandler(Object target) { this.target = target; }

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// アノテーションが付いているかチェック
if (method.isAnnotationPresent(LogExecutionTime.class)) {
long start = System.currentTimeMillis();
Object result = method.invoke(target, args);
System.out.println(“実行時間: ” + (System.currentTimeMillis() – start) + “ms”);
return result;
}
return method.invoke(target, args);
}
}

// 実行クラス
public class Main {
public static void main(String[] args) {
Service original = new ServiceImpl();
// 3. プロキシ生成
Service proxy = (Service) Proxy.newProxyInstance(
original.getClass().getClassLoader(),
original.getClass().getInterfaces(),
new TimingHandler(original)
);
proxy.execute();
}
}

応用・注意点

現場での利用には以下の点に注意してください。
1. インターフェース必須の制約: Java標準のProxyはインターフェースベースです。クラス単体には適用できません(CGLib等のライブラリが必要です)。
2. パフォーマンスへの影響: リフレクションを使用するため、多用しすぎるとオーバーヘッドが発生します。高頻度で呼ばれる箇所での利用は慎重に判断してください。
3. デバッグの難しさ: プロキシを介すとスタックトレースが複雑になります。ログ出力には十分注意を払いましょう。

この手法をマスターすると、Spring FrameworkのようなAOP(アスペクト指向プログラミング)の内部構造を深く理解できるようになり、設計の幅が大きく広がります。ぜひ試してみてください。

コメント

タイトルとURLをコピーしました