最新の安定バージョンについては、Spring Framework 6.2.6 を使用してください! |
プロキシメカニズム
Spring AOP は、JDK 動的プロキシまたは CGLIB を使用して、指定されたターゲットオブジェクトのプロキシを作成します。JDK 動的プロキシは JDK に組み込まれていますが、CGLIB は一般的なオープンソースクラス定義ライブラリです(spring-core
に再パッケージ化されています)。
プロキシされるターゲットオブジェクトが少なくとも 1 つのインターフェースを実装する場合、JDK 動的プロキシが使用されます。ターゲット型によって実装されるすべてのインターフェースがプロキシされます。ターゲットオブジェクトがインターフェースを実装しない場合、CGLIB プロキシが作成されます。
CGLIB プロキシの使用を強制する場合(たとえば、インターフェースによって実装されているメソッドだけでなく、ターゲットオブジェクトに対して定義されているすべてのメソッドをプロキシする)、そうすることができます。ただし、次の課題を考慮する必要があります。
CGLIB では、
final
メソッドはランタイム生成サブクラスでオーバーライドできないため、アドバイスできません。Spring 4.0 以降、CGLIB プロキシインスタンスは Objenesis を介して作成されるため、プロキシオブジェクトのコンストラクターが 2 回呼び出されることはなくなりました。JVM がコンストラクターのバイパスを許可しない場合にのみ、Spring の AOP サポートからの二重呼び出しと対応するデバッグログエントリが表示されることがあります。
CGLIB プロキシの使用は、JDK 9+ プラットフォームモジュールシステムで制限を受ける場合があります。一般的なケースとして、モジュールパスにデプロイする場合、
java.lang
パッケージのクラスの CGLIB プロキシを作成することはできません。このようなケースでは、モジュールで使用できない JVM ブートストラップフラグ--add-opens=java.base/java.lang=ALL-UNNAMED
が必要です。
CGLIB プロキシの使用を強制するには、次のように、<aop:config>
要素の proxy-target-class
属性の値を true に設定します。
<aop:config proxy-target-class="true">
<!-- other beans defined here... -->
</aop:config>
@AspectJ 自動プロキシサポートを使用するときに CGLIB プロキシを強制するには、次のように <aop:aspectj-autoproxy>
要素の proxy-target-class
属性を true
に設定します。
<aop:aspectj-autoproxy proxy-target-class="true"/>
複数の 明確にするために、 |
AOP プロキシについて
Spring AOP はプロキシベースです。独自のアスペクトを記述したり、Spring Framework で提供される Spring AOP ベースのアスペクトを使用する前に、最後のステートメントが実際に意味するセマンティクスを把握することが非常に重要です。
次のコードスニペットが示すように、まず、プレーンバニラ、プロキシ化されていない、特別なものは何もない、ストレートオブジェクト参照があるシナリオを考えます。
Java
Kotlin
public class SimplePojo implements Pojo {
public void foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this next method invocation is a direct call on the 'this' reference
this.bar()
}
fun bar() {
// some logic...
}
}
オブジェクト参照でメソッドを呼び出すと、次のイメージとリストに示すように、メソッドはそのオブジェクト参照で直接呼び出されます。
Java
Kotlin
public class Main {
public static void main(String[] args) {
Pojo pojo = new SimplePojo();
// this is a direct method call on the 'pojo' reference
pojo.foo();
}
}
fun main() {
val pojo = SimplePojo()
// this is a direct method call on the 'pojo' reference
pojo.foo()
}
クライアントコードの参照がプロキシの場合、状況はわずかに変わります。次の図とコードスニペットを検討してください。
Java
Kotlin
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
ここで理解しておくべき重要なことは、Main
クラスの main(..)
メソッド内のクライアントコードがプロキシへの参照を持っていることです。つまり、そのオブジェクト参照に対するメソッド呼び出しは、プロキシに対する呼び出しです。その結果、プロキシは、その特定のメソッド呼び出しに関連するすべてのインターセプター(アドバイス)に委譲できます。ただし、呼び出しが最終的にターゲットオブジェクト(この場合は SimplePojo
参照)に到達すると、this.bar()
や this.foo()
など、それ自体で行うメソッド呼び出しは、プロキシではなく this
参照に対して呼び出されます。これには重要な意味があります。つまり、自己呼び出しでは、メソッド呼び出しに関連するアドバイスが実行される可能性はありません。
では、これについてはどうすればいいのでしょうか? 最良の方法は (ここでは「最良」という言葉をゆるく使っています)、自己呼び出しが起こらないようにコードをリファクタリングすることです。これはあなたの多少の作業を必要としますが、これが最良であり、最も侵襲性の低いアプローチです。次のアプローチは非常に恐ろしいものですが、それを指摘することを躊躇しています。次の例が示すように、クラス内のロジックを Spring AOP に完全に結びつけることができます(私たちにとっては苦痛です)。
Java
Kotlin
public class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
class SimplePojo : Pojo {
fun foo() {
// this works, but... gah!
(AopContext.currentProxy() as Pojo).bar()
}
fun bar() {
// some logic...
}
}
これにより、コードが Spring AOP に完全に結合され、AOP に直面して飛行する AOP コンテキストで使用されているという事実がクラス自体に認識されます。また、次の例に示すように、プロキシを作成するときに追加の構成が必要です。
Java
Kotlin
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.addInterface(Pojo.class);
factory.addAdvice(new RetryAdvice());
factory.setExposeProxy(true);
Pojo pojo = (Pojo) factory.getProxy();
// this is a method call on the proxy!
pojo.foo();
}
}
fun main() {
val factory = ProxyFactory(SimplePojo())
factory.addInterface(Pojo::class.java)
factory.addAdvice(RetryAdvice())
factory.isExposeProxy = true
val pojo = factory.proxy as Pojo
// this is a method call on the proxy!
pojo.foo()
}
最後に、AspectJ はプロキシベースの AOP フレームワークではないため、この自己呼び出しの課題はありません。