cglibの紹介

cglibの概要

1. 概要

この記事では、cglib(コード生成ライブラリ)ライブラリについて説明します。 これは、HibernateSpringなどの多くのJavaフレームワークで使用されるバイトインストルメンテーションライブラリです。 バイトコードインスツルメンテーションにより、プログラムのコンパイルフェーズ後にクラスを操作または作成できます。

2. メーベン依存

プロジェクトでcglibを使用するには、Maven依存関係を追加するだけです(最新バージョンはhereにあります)。


    cglib
    cglib
    3.2.4

3. CGLIB

Javaのクラスは、実行時に動的にロードされます。 Cglibは、Java言語のこの機能を使用して、すでに実行中のJavaプログラムに新しいクラスを追加できるようにしています。

Hibernateは、動的プロキシの生成にcglibを使用します。 たとえば、データベースに格納されているオブジェクト全体を返すのではなく、オンデマンドでデータベースから値を遅延ロードするストアドクラスのインストルメントバージョンを返します。

Mockito,のような一般的なモックフレームワークは、モックメソッドにcglibを使用します。 モックは、メソッドが空の実装に置き換えられるインストルメントクラスです。

cglib.からの最も有用な構成を見ていきます

4. cglibを使用したプロキシの実装

2つのメソッドを持つPersonServiceクラスがあるとしましょう。

public class PersonService {
    public String sayHello(String name) {
        return "Hello " + name;
    }

    public Integer lengthOfName(String name) {
        return name.length();
    }
}

最初のメソッドはStringを返し、2番目のメソッドはInteger.を返すことに注意してください

4.1. 同じ値を返す

sayHello()メソッドの呼び出しをインターセプトする単純なプロキシクラスを作成したいと思います。 Enhancerクラスを使用すると、EnhancerクラスのsetSuperclass()メソッドを使用してPersonServiceクラスを動的に拡張することにより、プロキシを作成できます。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((FixedValue) () -> "Hello Tom!");
PersonService proxy = (PersonService) enhancer.create();

String res = proxy.sayHello(null);

assertEquals("Hello Tom!", res);

FixedValueは、プロキシされたメソッドから値を返すだけのコールバックインターフェイスです。 プロキシでsayHello()メソッドを実行すると、プロキシメソッドで指定された値が返されました。

4.2. メソッドシグネチャに応じた戻り値

プロキシの最初のバージョンには、プロキシがインターセプトするメソッドとスーパークラスから呼び出すメソッドを決定できないため、いくつかの欠点があります。 MethodInterceptorインターフェイスを使用して、プロキシへのすべての呼び出しをインターセプトし、特定の呼び出しを行うか、スーパークラスからメソッドを実行するかを決定できます。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(PersonService.class);
enhancer.setCallback((MethodInterceptor) (obj, method, args, proxy) -> {
    if (method.getDeclaringClass() != Object.class && method.getReturnType() == String.class) {
        return "Hello Tom!";
    } else {
        return proxy.invokeSuper(obj, args);
    }
});

PersonService proxy = (PersonService) enhancer.create();

assertEquals("Hello Tom!", proxy.sayHello(null));
int lengthOfName = proxy.lengthOfName("Mary");

assertEquals(4, lengthOfName);

この例では、メソッドシグネチャがObjectクラスからのものではない場合、すべての呼び出しをインターセプトしています。 toString()またはhashCode()メソッドはインターセプトされません。 それに加えて、Stringを返すPersonServiceからのメソッドのみをインターセプトしています。 戻り値の型がInteger.であるため、lengthOfName()メソッドの呼び出しはインターセプトされません。

5. BeanCreator

cglibからのもう1つの便利な構成は、BeanGeneratorクラスです。 これにより、Beanを動的に作成し、setterメソッドとgetterメソッドとともにフィールドを追加できます。 コード生成ツールで使用して、単純なPOJOオブジェクトを生成できます。

BeanGenerator beanGenerator = new BeanGenerator();

beanGenerator.addProperty("name", String.class);
Object myBean = beanGenerator.create();
Method setter = myBean.getClass().getMethod("setName", String.class);
setter.invoke(myBean, "some string value set by a cglib");

Method getter = myBean.getClass().getMethod("getName");
assertEquals("some string value set by a cglib", getter.invoke(myBean));

6. Mixinの作成

mixinは、複数のオブジェクトを1つに結合できるようにする構成です。 いくつかのクラスの動作を含めて、その動作を単一のクラスまたはインターフェースとして公開できます。 cglibミックスインを使用すると、複数のオブジェクトを1つのオブジェクトに組み合わせることができます。 ただし、そのためには、ミックスインに含まれるすべてのオブジェクトをインターフェイスで裏付ける必要があります。

2つのインターフェイスのミックスインを作成するとします。 インターフェースとその実装の両方を定義する必要があります。

public interface Interface1 {
    String first();
}

public interface Interface2 {
    String second();
}

public class Class1 implements Interface1 {
    @Override
    public String first() {
        return "first behaviour";
    }
}

public class Class2 implements Interface2 {
    @Override
    public String second() {
        return "second behaviour";
    }
}

Interface1Interface2の実装を構成するには、両方を拡張するインターフェースを作成する必要があります。

public interface MixinInterface extends Interface1, Interface2 { }

Mixinクラスのcreate()メソッドを使用することにより、Class1およびClass2の動作をMixinInterface:に含めることができます。

Mixin mixin = Mixin.create(
  new Class[]{ Interface1.class, Interface2.class, MixinInterface.class },
  new Object[]{ new Class1(), new Class2() }
);
MixinInterface mixinDelegate = (MixinInterface) mixin;

assertEquals("first behaviour", mixinDelegate.first());
assertEquals("second behaviour", mixinDelegate.second());

mixinDelegateでメソッドを呼び出すと、Class1Class2.から実装が呼び出されます。

7. 結論

この記事では、cglibとその最も有用な構成要素について説明しました。 Enhancerクラスを使用してプロキシを作成しました。 BeanCreatorを使用し、最後に、他のクラスの動作を含むMixinを作成しました。

Cglibは、Springフレームワークで広く使用されています。 Springでcglibプロキシを使用する1つの例は、メソッド呼び出しにセキュリティ制約を追加することです。 メソッドを直接呼び出す代わりに、Springセキュリティは最初に(プロキシを介して)指定されたセキュリティチェックが成功したかどうかを確認し、この検証が成功した場合にのみ実際のメソッドに委任します。 この記事では、私たち自身の目的のためにそのようなプロキシを作成する方法を見ました。

これらすべての例とコードスニペットの実装は、GitHub projectにあります。これはMavenプロジェクトであるため、そのままインポートして実行するのは簡単です。