Spring AOP(Aspect Oriented Programming)は、アスペクト指向プログラミングをベースにしたフレームワークで、元の業務コードを変更することなく、複数の対象オブジェクトを一元管理したり、機能を追加したりすることができる。実装プロセスは主にプロキシ (Proxy) および動的プロキシ (Dynamic Proxy) テクノロジーに依存しますが、この記事では Spring AOP の実装原理を詳細に分析します。
1. AOPの基本概念
AOP は、アプリケーションを重要な部分に分解し、それらの部分にまたがる懸念事項を分離することで、ビジネス ロジックをより適切に定義するのに役立ちます。その中核となる概念には次のようなものがあります。
-
アスペクト (アスペクト) アスペクトは、ターゲット クラスに組み込まれた関数コードであり、Java の場合、メソッド、コンストラクター、またはクラスの特定のポイントのコードになります。側面には、事前通知、事後通知、サラウンド通知、例外通知、最終通知などが含まれ、ビジネス ニーズに応じてカスタマイズできます。
-
接続ポイント (結合ポイント) 接続ポイントは、アスペクトがターゲット オブジェクトに組み込まれる場所を示します。Java の場合、通常はメソッドの実行ポイントまたはコンストラクターの作成ポイントです。
-
ポイントカット (ポイントカット) ポイントカットは接続ポイントのコレクションであり、どの接続ポイントをターゲット オブジェクトに織り込む必要があるかを定義します。
-
アドバイス(アドバイス) アドバイスには、プレアドバイス、ポストアドバイス、サラウンドアドバイス、例外アドバイス、ファイナルアドバイスなどの各種アスペクトコードが含まれる。
-
ウィービング ウィービングは、アスペクト コードをターゲット オブジェクトに織り込むプロセスであり、コンパイル時、ロード時、または実行時に発生します。Spring AOP は、ウィービングの主な方法として動的プロキシ テクノロジを使用します。
-
はじめに 「はじめに」を使用すると、元のクラス定義では定義されていないが、インポートされたインターフェイスで定義されている新しいメソッドとプロパティを既存のクラスに追加できます。
2 つまたは 3 つの AOP 実装方法
Spring AOP の実装原理を紹介する前に、まず AOP を実装する 3 つの方法 (静的プロキシ、JDK の Proxy クラスを使用して動的プロキシを実装する、および CGLIB を使用して動的プロキシを実装する) を理解します。
Spring AOP の実装原理と AOP が動的プロキシを使用する方法をより深く理解するには、静的プロキシ、JDK 動的プロキシ、および CGLIB 動的プロキシの 3 つの方法を深く理解することができます。
1. 静的プロキシ
静的プロキシとは、プロキシする必要があるクラスとメソッドがコンパイル時に決定されることを指します。プロキシ クラスはコンパイル時にすでに存在します。利点は動作効率が高いですが、欠点は柔軟性に欠け、事前にプロキシすることしかできないことです。 -定義されたクラスとメソッド。以下は、静的プロキシの原理を示す簡単な例です。
まず、インターフェイス Subject を定義します。
public interface Subject {
void request();
}
次に、ターゲット オブジェクト RealSubject を定義します。
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject is handling the request.");
}
}
最後に、プロキシ オブジェクト ProxySubject を定義します。
public class ProxySubject implements Subject {
private Subject realSubject;
public ProxySubject(Subject realSubject) {
this.realSubject = realSubject;
}
@Override
public void request() {
System.out.println("ProxySubject is handling the request.");
realSubject.request();
}
}
上記の例では、ProxySubject はプロキシ オブジェクトであり、Subject インターフェイスを実装し、実際のオブジェクト realSubject を保持し、request() メソッドに追加の処理ロジックを追加します。
Subject インターフェイスでメソッドを呼び出す必要がある場合は、次のようにプロキシ オブジェクト ProxySubject を作成することでメソッドを呼び出すことができます。
public static void main(String[] args) {
Subject subject = new RealSubject();
subject.request();
Subject proxySubject = new ProxySubject(subject);
proxySubject.request();
}
プログラムを実行すると、次の出力が表示されます。
RealSubject is handling the request.
ProxySubject is handling the request.
RealSubject is handling the request.
上記の例では、プロキシ オブジェクト ProxySubject が request() メソッドを呼び出すと、最初に「ProxySubject がリクエストを処理しています。」という文が出力され、次にターゲット オブジェクト RealSubject の request() メソッドが呼び出されます。
2.JDK動的プロキシ
JDK 動的プロキシは、Java に付属する Proxy クラスと InvocationHandler インターフェイスを通じてプロキシ クラスを動的に生成するプロセスを指します。実行時に動的に生成されるプロキシ クラスは、任意のインターフェイスを実装できます。静的プロキシと比較すると、より柔軟ですが、実装できるのはインターフェイスを持つクラスは、インターフェイスを実装していないクラスをプロキシすることはできません。以下は、JDK 動的プロキシの原理を示す簡単な例です。
まず、インターフェイス Subject を定義します。
public interface Subject {
void request();
}
次に、ターゲット オブジェクト RealSubject を定義します。
public class RealSubject implements Subject {
@Override
public void request() {
System.out.println("RealSubject is handling the request.");
}
}
次に、InvocationHandler 実装クラス MyInvocationHandler を定義します。
public class MyInvocationHandler implements InvocationHandler {
private Object target;
public MyInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("ProxySubject is handling the request.");
Object result = method.invoke(target, args);
return result;
}
}
上記の例では、MyInvocationHandler は InvocationHandler インターフェイスを実装し、invoke() メソッドを書き換えて、このメソッドに追加の処理ロジックを追加します。
最後に、メイン プログラムで動的プロキシ オブジェクトを作成します。
public static void main(String[] args) {
Subject realSubject = new RealSubject();
InvocationHandler invocationHandler = new MyInvocationHandler(realSubject);
Subject proxySubject = (Subject) Proxy.newProxyInstance(realSubject.getClass().getClassLoader(), realSubject.getClass().getInterfaces(), invocationHandler);
proxySubject.request();
}
プログラムを実行すると、次の出力が表示されます。
ProxySubject is handling the request.
RealSubject is handling the request.
上記の例では、プロキシ オブジェクト proxySubject は、Proxy.newProxyInstance() メソッドを呼び出すことによって作成されます。このメソッドは、Subject インターフェイスを実装する任意のクラスをプロキシできます。
3. CGLIB 動的プロキシ
CGLIB ダイナミック プロキシは、CGLIB ライブラリを使用してプロキシ クラスを動的に生成するプロセスを指します。JDK ダイナミック プロキシとは異なり、CGLIB ダイナミック プロキシは、インターフェイスを実装していないクラスをプロキシすることができ、より幅広い適用性を備えています。以下は、CGLIB 動的プロキシの原理を示す簡単な例です。
まずターゲット オブジェクト RealSubject を定義します。
public class RealSubject {
public void request() {
System.out.println("RealSubject is handling the request.");
}
}
次に、MethodInterceptor 実装クラス MyMethodInterceptor を定義します。
public class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("ProxySubject is handling the request.");
Object result = proxy.invokeSuper(obj, args);
return result;
}
}
上記の例では、MyMethodInterceptor は MethodInterceptor インターフェイスを実装し、intercept() メソッドを書き換えて、このメソッドに追加の処理ロジックを追加します。
最後に、メイン プログラムで動的プロキシ オブジェクトを作成します。
public static void main(String[] args) {
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(RealSubject.class);
enhancer.setCallback(new MyMethodInterceptor());
RealSubject proxySubject = (RealSubject) enhancer.create();
proxySubject.request();
}
プログラムを実行すると、次の出力が表示されます。
ProxySubject is handling the request.
RealSubject is handling the request.
上記の例では、プロキシ オブジェクト proxySubject は、Enhancer.create() メソッドを呼び出すことによって作成されます。このメソッドは、インターフェイスを実装していないクラスを含む任意のクラスをプロキシできます。
一般に、これら 3 つの方法では、静的プロキシと JDK 動的プロキシがより広く使用されています。要件が比較的単純で、少数のインターフェイスまたはクラスのみをプロキシする必要がある場合は、静的プロキシと JDK 動的プロキシを選択できます。CGLIB 動的プロキシプロキシは比較的柔軟性があり、次のようなことができます。プロキシはインターフェイスのクラスを実装しませんが、ASM バイトコード操作ライブラリを使用するため、JDK 動的プロキシよりも若干効率が劣ります。したがって、AOP の実装を選択する場合は、特定のニーズに応じて適切な実装方法を選択する必要があります。
3、Spring AOP 実装原則
Spring AOP は主に、AOP Alliance が提供する標準 API を通じて実装されており、コア インターフェイスには次のものが含まれます。
-
Advisor Advisor はアスペクトの基本クラスであり、Join Point と Advice に関連付けられており、外部から呼び出す場合にのみ Advisor を処理する必要があります。
-
ポイントカット ポイントカットは、AOP 内のどの接続ポイントをアスペクト コードに組み込む必要があるかを記述するために使用され、ポイントカット式と呼ばれるパターンに従って接続ポイントを照合できます。
-
アドバイス アドバイスは、Before、AfterReturning、Around、AfterThrowing、After などのアスペクトの特定のロジックです。
-
JoinPoint JoinPoint は対象オブジェクトの特定の接続点を表し、接続点に関する情報は Advice で取得できます。
-
ProxyFactory ProxyFactory は、動的なプロキシ オブジェクトを作成し、アスペクトをターゲット オブジェクトに織り込む役割を担うプロキシ ファクトリであり、同時にさまざまなデコレータを追加してさまざまな機能を実現できます。
以下では、簡単な例を使用して Spring AOP の実装原理を詳しく説明します。
まず Person クラスを定義します。
public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public void say() {
System.out.println("Hello, my name is " + name + ", I'm " + age + " years old.");
}
}
次に、アスペクト クラス LogAspect を定義します。
public class LogAspect {
public void before(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " is invoked before...");
}
public void after(JoinPoint joinPoint) {
System.out.println("Method " + joinPoint.getSignature().getName() + " is invoked after...");
}
}
上記の例では、LogAspect はアスペクト クラスであり、前後に 2 つの通知メソッドが含まれています。
別のテスト クラスを定義します。
public class Test {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
Person person = (Person) context.getBean("person");
person.say();
}
}
最後に applicationContext.xml で設定します。
<bean id="person" class="com.example.Person">
<constructor-arg value="Tom"></constructor-arg>
<constructor-arg value="18"></constructor-arg>
</bean>
<bean id="logAspect" class="com.example.LogAspect"></bean>
<aop:config>
<aop:aspect id="log" ref="logAspect">
<aop:before method="before" pointcut="execution(* com.example.Person.say(..))"></aop:before>
<aop:after method="after" pointcut="execution(* com.example.Person.say(..))"></aop:after>
</aop:aspect>
</aop:config>
このうち、aop:configタグは AOP プロキシの設定に使用され、aop:aspectタグはアスペクト情報の設定に使用され、pointcut 属性はエントリ ポイントの指定に使用され、method 属性は通知の指定に使用されます。方法。
プログラムを実行すると、次の結果が出力されることがわかります。
Method say is invoked before...
Hello, my name is Tom, I'm 18 years old.
Method say is invoked after...
上記の例では、Spring AOP の実装原理には主に次のステップが含まれています。
-
Spring 構成ファイルに従って Bean 定義を解析し、対応する Java Bean オブジェクトを作成します。
-
Bean オブジェクトを作成するときに、そのオブジェクトがプロキシされるように宣言されている場合、動的プロキシ オブジェクトが作成され、Bean によって定義されたすべての Advisor に従って返されます。
-
動的プロキシ オブジェクトを作成するプロセスで、Spring は JDK 独自のライブラリまたは CGLIB などのサードパーティ ライブラリを通じてプロキシ クラスのバイトコードを作成し、それを JVM メモリに動的にロードします。
-
ターゲット メソッドを呼び出す前に、動的プロキシ オブジェクトはまず対応するアスペクト ロジックを呼び出し、次にターゲット メソッド自体を呼び出します。
-
ターゲット メソッドが実行された後、動的プロキシ オブジェクトは対応するアスペクト ロジックを再度呼び出して、プロキシ プロセス全体を完了します。
4. まとめ
Spring AOP は、プロキシおよび動的プロキシ テクノロジに基づくアスペクト プログラミング フレームワークであり、ビジネス ロジックを複数の部分に分解することでコードの再利用性と保守性を向上させます。実装プロセスでは、Spring AOP は主に Advisor、Pointcut、Advice、JoinPoint、ProxyFactory などのコア インターフェイスを通じてアスペクト コードを管理し、織り込みます。Spring AOP を使用する場合、アスペクト クラス、結合ポイント、およびポイントカットを定義し、Spring 構成ファイルのaop:configおよびaop:aspectタグを通じてさまざまなアドバイス タイプを指定する必要があります。