1. 概要

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

2. Mavenの依存関係

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

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

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からのメソッドのみをインターセプトしています。 lengthOfName()メソッドの呼び出しは、その戻りタイプが Integer。であるため、インターセプトされません。

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 でメソッドを呼び出すと、Class1およびClass2。から実装が呼び出されます。

7. 結論

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

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

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