プロキシデザインパターン の場合、 JDKの動的プロキシ と CGLib などのサードパーティの動的コード生成APIとの違いは何ですか?
両方のアプローチを使用することと、あるアプローチを別のアプローチより優先する場合の違いは何ですか?
JDKダイナミックプロキシは、インターフェイスごとにのみプロキシできます(したがって、ターゲットクラスはインターフェイスを実装する必要があり、このインターフェイスはプロキシクラスによっても実装されます)。
CGLIB(およびjavassist)は、サブクラス化によりプロキシを作成できます。このシナリオでは、プロキシはターゲットクラスのサブクラスになります。インターフェイスは必要ありません。
したがって、Java動的プロキシはプロキシできます:public class Foo implements iFoo
CGLIBはプロキシできます:public class Foo
編集:
JavassistとCGLIBはサブクラス化によってプロキシを使用するため、これに依存するフレームワークを使用する場合、finalメソッドを宣言したり、クラスをfinalにしたりできない理由に言及する必要があります。これは、これらのライブラリがクラスをサブクラス化してメソッドをオーバーライドすることを禁止します。
機能の違い
JDKプロキシでは、Object
をサブクラス化しながら、任意のインターフェイスセットを実装できます。次に、インターフェイスメソッドplusObject::hashCode
、Object::equals
、およびObject::toString
が InvocationHandler
に転送されます。さらに、標準ライブラリインターフェイスJava.lang.reflect.Proxy
が実装されています。
cglibを使用すると、非最終クラスをサブクラス化しながら、インターフェイスのセットを実装できます。また、メソッドはオプションでオーバーライドできます。つまり、すべての非抽象メソッドをインターセプトする必要はありません。さらに、メソッドを実装するさまざまな方法があります。また、InvocationHandler
クラス(別のパッケージ内)を提供しますが、たとえばMethodInterceptor
などのより高度なインターセプターを使用してスーパーメソッドを呼び出すこともできます。さらに、cglibはFixedValue
のような特殊なインターセプトによってパフォーマンスを改善できます。私はかつて cglibのさまざまなインターセプターの要約 と書きました。
パフォーマンスの違い
JDKプロキシは、InvocationHandler
というインターセプトディスパッチャを1つだけ使用して、かなり単純に実装されます。これには、常にインライン化できない実装への仮想メソッドのディスパッチが必要です。 Cglibでは、パフォーマンスを改善できる特殊なバイトコードを作成できます。 18のスタブメソッドを持つインターフェイスを実装するための比較を次に示します。
cglib JDK proxy
creation 804.000 (1.899) 973.650 (1.624)
invocation 0.002 (0.000) 0.005 (0.000)
時間はナノ秒単位で示され、標準偏差は中括弧で示されます。ベンチマークの詳細については、 Byte Buddyのチュートリアル を参照してください。ByteBuddyはcglibのより新しい代替手段です。また、cglibは現在活発に開発されていないことに注意してください。
動的プロキシ:JDK Reflection APIを使用した実行時のインターフェースの動的実装。
例:Springは、トランザクションの動的プロキシを次のように使用します。
生成されたプロキシはBeanの上にあります。 Beanに国境を越えた振る舞いを追加します。ここでは、プロキシはJDK Reflection APIを使用して実行時に動的に生成します。
アプリケーションが停止すると、プロキシが破棄され、ファイルシステム上にインターフェイスとBeanのみが存在します。
上記の例にはインターフェースがあります。しかし、ほとんどのインターフェイスの実装では最適ではありません。したがって、Beanはインターフェースを実装しません。その場合、継承を使用します。
そのようなプロキシを生成するために、SpringはCGLibと呼ばれるサードパーティのライブラリを使用します。
CGLib(CodeGenerationLibrary)は ASM の上に構築され、主にプロキシ拡張Beanの生成に使用され、プロキシメソッドにBeanの動作を追加します。
Spring AOPは、JDK動的プロキシまたはCGLIBを使用して、特定のターゲットオブジェクトのプロキシを作成します。 (選択肢がある場合は、JDK動的プロキシが推奨されます)。
プロキシされるターゲットオブジェクトが少なくとも1つのインターフェイスを実装する場合、JDK動的プロキシが使用されます。ターゲットタイプによって実装されるすべてのインターフェイスがプロキシされます。ターゲットオブジェクトがインターフェイスを実装しない場合、CGLIBプロキシが作成されます。
CGLIBプロキシの使用を強制する場合(たとえば、インターフェイスによって実装されたメソッドだけでなく、ターゲットオブジェクトに対して定義されたすべてのメソッドをプロキシするため)、そうすることができます。ただし、考慮すべき問題がいくつかあります。
最終メソッドはオーバーライドできないため、アドバイスできません。
ダイナミックプロキシはJDKで使用できますが、クラスパスにはCGLIB 2バイナリが必要です。 Springは、CGLIBが必要で、CGLIBライブラリクラスがクラスパスに見つからない場合、自動的に警告します。
プロキシされたオブジェクトのコンストラクターは2回呼び出されます。これは、プロキシされるオブジェクトごとにサブクラスが生成されるCGLIBプロキシモデルの自然な結果です。プロキシされたインスタンスごとに、実際のプロキシされたオブジェクトと、アドバイスを実装するサブクラスのインスタンスの2つのオブジェクトが作成されます。 JDKプロキシを使用する場合、この動作は見られません。通常、プロキシされた型のコンストラクターを2回呼び出すことは問題ではありません。通常、割り当てが行われるだけで、コンストラクターには実際のロジックは実装されていません。