CGLIB動的プロキシの内部実装とソースコード解析

CGLIBによる動的プロキシの概要

CGLIB(Code Generation Library)は、実行時にJavaクラスを拡張したり、インタフェースを実装したりできる高パフォーマンスなバイトコード生成ライブラリです。内部ではASMという軽量かつ高速なバイトコード操作フレームワークを用いており、クラスファイルの直接操作や新規クラスの生成を可能にしています。

ASMを直接使うことも可能ですが、JVM内部構造やclassファイルフォーマット、バイトコード命令セットへの深い理解が必要です。そのため、CGLIBのような抽象化されたAPIが広く使われています。

CGLIBはJDK標準には含まれていないため、Maven経由で依存関係を追加する必要があります:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

基本的な使用例

まず、プロキシ対象となるクラスを定義します:

public class Student {
    public void study() {
        System.out.println("学生として勉強したい");
    }
}

次に、MethodInterceptorインタフェースを実装してメソッド呼び出しを横断的に処理します:

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class LearningInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("【前処理】");
        Object result = proxy.invokeSuper(target, args);
        System.out.println("【後処理】");
        return result;
    }
}

プロキシインスタンスの生成にはEnhancerクラスを使用します:

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;

public class ProxyDemo {
    public static void main(String[] args) {
        // 生成されたプロキシクラスをローカルに保存
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/tmp/cglib");

        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Student.class);
        enhancer.setCallback(new LearningInterceptor());
        Student student = (Student) enhancer.create();
        student.study();
    }
}

実行結果:

【前処理】
学生として勉強したい
【後処理】

指定ディレクトリには、CGLIBが自動生成したクラスファイル(例:Student$$EnhancerByCGLIB$$xxxxx.class)が出力されます。

内部実装の詳細

Enhancer.create()メソッドは、必要に応じて新しいサブクラスを生成し、そのインスタンスを返します。内部では以下の流れで処理されます:

  1. createHelper()内でキーを生成(スーパークラス名、コールバックタイプなどからハッシュ化)
  2. キャッシュまたは新規生成によりクラスを取得
  3. AbstractClassGenerator.create()でクラスローダーを特定し、キャッシュ管理
  4. 最終的にnextInstance()でコンストラクタ経由でインスタンスを生成

生成されたプロキシクラスは元のクラスを継承しており、各メソッドは次のようにオーバーライドされています:

public final void study() {
    MethodInterceptor interceptor = this.CGLIB$CALLBACK_0;
    if (interceptor == null) {
        CGLIB$BIND_CALLBACKS(this);
        interceptor = this.CGLIB$CALLBACK_0;
    }
    if (interceptor != null) {
        interceptor.intercept(this, CGLIB$study$0$Method, CGLIB$emptyArgs, CGLIB$study$0$Proxy);
    } else {
        super.study();
    }
}

このように、すべてのメソッド呼び出しはユーザー定義のintercept()メソッドを経由し、前処理・後処理を挿入できます。

JDK動的プロキシとの比較

  • 実装方式:JDKプロキシはインタフェースに基づくリフレクションとInvocationHandlerを使用。CGLIBはバイトコード操作によりサブクラスを生成。
  • 制約:JDKプロキシはインタフェースの実装を必須とするが、CGLIBは任意のクラス(ただしfinalクラスを除く)に適用可能。
  • パフォーマンス:JDK 1.6以降、JDKプロキシの性能が大幅に改善され、多くのケースでCGLIBとの差は縮小している。

タグ: CGLIB Java Bytecode proxy ASM

6月5日 17:32 投稿