技術共有

プロキシの詳細説明: 静的プロキシ、動的プロキシ、Spring AOP 実装

2024-07-12

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina

1. エージェント紹介

プロキシとは、オブジェクト A が別のオブジェクト B を保持することで、B と同じ動作をさせることができるモードを指します。プロトコルを外部に公開するために、B がインターフェイスを実装することが多く、A もそのインターフェイスを実装します。ただし、B は「実際の」実装クラスですが、A は B のメソッドを借用してインターフェイス メソッドを実装します。 A は「疑似軍隊」ですが、B のメソッドを呼び出す前後で B を強化したり、他のことを行うことができます。 Spring AOP は動的プロキシを使用して、コードの動的な「ウィービング」を完了します。

プロキシを使用する利点はこれ以外にもあります。プロジェクトが別のプロジェクトが提供するインターフェイスに依存しているが、他のプロジェクトのインターフェイスが不安定で、プロトコルが頻繁に変更される場合は、インターフェイスが変更されたときにプロキシを使用できます。ビジネス コードを 1 つずつ変更する必要はなく、プロキシのみを変更する必要があります。この意味で、外部のコードがコードに侵入するのを防ぐために、外部の世界に適応するすべてのインターフェイスに対してこれを行うことができます。これは防御的プログラミングと呼ばれます。プロキシ用のアプリケーションは他にもたくさんあるかもしれません。

上記の例では、クラス A は、B の静的プロキシである B を保持するようにハードコーディングされています。 A プロキシの対象が不確かな場合、それは動的プロキシです。現在、動的プロキシには、jdk 動的プロキシと cglib 動的プロキシという 2 つの一般的な実装があります。

2. 静的プロキシ

静的プロキシはデザイン パターンであり、プロキシ パターンの一種です。静的プロキシでは、プロキシ クラスはプログラムの実行前に定義されており、プロキシ クラスとプロキシ クラスの関係はコンパイル時に決定されます。これは、プロキシ クラスとプロキシ クラスの両方が同じインターフェイスを実装するか、同じ親クラスを継承することを意味します。プロキシ クラスは内部的にプロキシ クラスのインスタンスを保持し、同時にプロキシ クラスのメソッドを独自のメソッドで呼び出します。 、呼び出しの前後に、ログ記録、権限チェック、トランザクション処理などの独自の操作を追加できます。

静的プロキシには、抽象インターフェイス、プロキシ クラス、およびプロキシ クラスの 3 つのコンポーネントがあります。その実装例は次のとおりです。

1) 抽象インターフェースを定義する

  1. public interface TargetInteface {
  2. void method1();
  3. void method2();
  4. int method3(Integer i);
  5. }

2) プロキシクラスを定義する

  1. public class TargetProxy implements TargetInteface {
  2. private Target target =new Target();
  3. @Override
  4. public void method1() {
  5. System.out.println("执行方法前...");
  6. target.method1();
  7. System.out.println("执行方法后...");
  8. }
  9. @Override
  10. public void method2() {
  11. System.out.println("执行方法前...");
  12. target.method2();
  13. System.out.println("执行方法后...");
  14. }
  15. @Override
  16. public int method3(Integer i) {
  17. System.out.println("执行方法前...");
  18. int method3 = target.method3(i);
  19. System.out.println("执行方法后...");
  20. return method3;
  21. }
  22. }

3) プロキシクラスを定義する

  1. public class Target implements TargetInteface {
  2. @Override
  3. public void method1() {
  4. System.out.println(" Target method1 running ...");
  5. }
  6. @Override
  7. public void method2() {
  8. System.out.println("Target method2 running ...");
  9. }
  10. @Override
  11. public int method3(Integer i) {
  12. System.out.println("Target method3 running ...");
  13. return i;
  14. }
  15. }

4) クライアントを定義し、実行結果を確認する

  1. public class TargetUser {
  2. public static void main(String[] args) {
  3. TargetInteface target = new TargetProxy();
  4. target.method1();
  5. System.out.println("-----------------------------");
  6. target.method2();
  7. System.out.println("-----------------------------");
  8. System.out.println(target.method3(3));
  9. }
  10. }

結果出力:

メソッドを実行する前に...
ターゲットメソッド1が実行中です...
メソッドを実行した後...
-----------------------------
メソッドを実行する前に...
ターゲットメソッド2が実行中です...
メソッドを実行した後...
-----------------------------
メソッドを実行する前に...
ターゲットメソッド3が実行中...
メソッドを実行した後...
3

静的エージェントの実装から、静的エージェントの利点は実装が単純で理解しやすいことであることがわかります。しかし、その欠点も明らかです。つまり、新しいクラスにプロキシ機能を追加する必要がある場合は常に、新しいプロキシ クラスを手動で作成する必要があり、クラス数の急激な増加とメンテナンス コストの増加につながります。 。同時に、プロキシ クラスとプロキシ クラス間の結合度が高すぎるため、プロキシ クラスでメソッドを追加、削除、または変更すると、プロキシ クラスでも対応するメソッドを追加、削除、または変更する必要があります。これにより、コードのメンテナンスのコストが向上します。もう 1 つの問題は、プロキシ オブジェクトが複数のターゲット インターフェイスの実装クラスをプロキシする場合、プロキシ オブジェクトはターゲット オブジェクトと同じインターフェイス (実際には包含関係) を実装する必要があるため、複数の実装クラスに異なるメソッドが存在する必要があることです。多くのメソッドを使用すると、コードが肥大化し、保守が困難になる可能性があります。

3. 動的プロキシ

動的プロキシの中心となるアイデアは、元のオブジェクト コードを変更せずに、プロキシ オブジェクトを介して元のオブジェクトに間接的にアクセスし、アクセスの前後に追加の操作を実行することです。

動的プロキシの実装原理は主に Java のリフレクション メカニズムに基づいています。動的プロキシを使用する場合、プロキシされたクラス (プロキシされたオブジェクト) の動作を定義するインターフェイスまたはインターフェイスのセットを定義する必要があります。次に、実装を書く必要がありますInvocationHandlerインターフェイス クラス。このクラスには、プロキシ オブジェクトのメソッド呼び出しの前後に実行されるロジックが含まれています。電話をかけるときProxy.newProxyInstance()メソッド、インターフェイスのクラスローダー、インターフェイス配列、およびInvocationHandlerオブジェクト、Java は実行時に指定されたインターフェイスを実装するプロキシ クラスを動的に生成し、メソッド呼び出しをInvocationHandler処理するオブジェクト。プロキシ オブジェクトのメソッドが呼び出されるとき、実際に呼び出されます。InvocationHandlerインターフェースinvoke()メソッド。メソッド名、パラメーター、その他の情報に基づいて前処理ロジックを実行し、リフレクションを通じてプロキシ オブジェクトの対応するメソッドを呼び出すことができます。

次に、JDK プロキシと CGLib という 2 つの動的プロキシを紹介します。

1)JDKプロキシ

① JDK Proxyの内部仕組み

JDK プロキシは、Java のリフレクション メカニズムを使用して、プロキシ クラスを動的に生成します。具体的には、Proxyクラスが使用しますProxyGeneratorクラス (このクラスはパブリック API ではありませんが、JDK 内で動的プロキシを実装するための鍵です) を使用して、プロキシ クラスのバイトコードを生成し、それを JVM にロードします。生成されたプロキシ クラスは次のものを継承します。java.lang.reflect.Proxyクラスを作成し、指定されたインターフェイスを実装します。プロキシクラスのメソッド内で呼び出されます。InvocationHandlerinvokeメソッド。メソッド呼び出しを処理のためにプロセッサに転送します。

さらに、パフォーマンスを向上させるために、JDK プロキシは、生成されたプロキシ クラスの Class オブジェクトをキャッシュするためのキャッシュ メカニズムも提供します。これにより、同じ型のプロキシ オブジェクトを作成する必要がある場合に、プロキシ クラスの Class オブジェクトを再生成せずにキャッシュから直接取得できます。キャッシュは経由で行われますWeakCacheこのクラスによって実装されると、弱参照を使用してオブジェクトをキャッシュし、JVM がガベージ コレクションを実行するときに、使用されなくなったキャッシュ項目を自動的にクリーンアップできるようにします。

② JDK Proxyの実装手順

  • インターフェースとプロキシされたクラスを定義する: まず、プロキシ クラスによって実装される 1 つ以上のインターフェイスを定義します。
  1. public interface TargetInteface {
  2. void method1();
  3. void method2();
  4. int method3(Integer i);
  5. }
  1. public class Target implements TargetInteface {
  2. @Override
  3. public void method1() {
  4. System.out.println("method1 running ...");
  5. }
  6. @Override
  7. public void method2() {
  8. System.out.println("method2 running ...");
  9. }
  10. @Override
  11. public int method3(Integer i) {
  12. System.out.println("method3 running ...");
  13. return i;
  14. }
  15. }
  • CreateInvocationHandler:成し遂げるInvocationHandlerインターフェースと書き換えinvoke方法。存在するinvokeこのメソッドでは、ロギング、権限チェックなどのカスタム ロジックを追加し、リフレクションを通じて元のクラス メソッドを呼び出すことができます。
  • プロキシオブジェクトの生成:移行Proxy.newProxyInstanceメソッド、クラスローダー、インターフェイス配列、およびInvocationHandlerプロキシ オブジェクトを動的に生成するインスタンス。このメソッドは、指定されたインターフェイスを実装するプロキシ クラス インスタンスを返します。
  1. public class TargetProxy {
  2. public static <T> Object getTarget(T t) {
  3. //新构建了一个 新的 代理类的对象
  4. return Proxy.newProxyInstance(t.getClass().getClassLoader(), t.getClass().getInterfaces(), new InvocationHandler() {
  5. @Override
  6. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  7. // proxy就是目标对象t,method就是调用目标对象中方法,args就是调用目标对象中方法的参数。
  8. //比如说:代理对象.method1(),这时proxy就是目标类,method1就是method,args就是method1方法参数。
  9. System.out.println("执行方法前...");
  10. Object invoke = method.invoke(t, args);
  11. System.out.println("执行方法后...");
  12. return invoke;
  13. }
  14. });
  15. }
  16. }
  • プロキシ オブジェクトを使用する: プロキシ オブジェクトを通じてメソッドが呼び出される場合、実際にはメソッドが呼び出されます。InvocationHandlerinvokeメソッドは、このメソッド内でカスタムロジックを実行した後、元のクラスのメソッドを呼び出します。
  1. public class TargetUser {
  2. public static void main(String[] args) {
  3. TargetInteface target = (TargetInteface) TargetProxy.getTarget(new Target());
  4. target.method1();
  5. System.out.println("-----------------------------");
  6. target.method2();
  7. System.out.println("-----------------------------");
  8. System.out.println(target.method3(3));
  9. }
  10. }

結果出力:
メソッドを実行する前に...
method1 実行中...
メソッドを実行した後...
-----------------------------
メソッドを実行する前に...
method2 実行中...
メソッドを実行した後...
-----------------------------
メソッドを実行する前に...
method3 実行中...
メソッドを実行した後...
3

③ JDKプロキシの特徴

  1. インターフェースプロキシ: JDK プロキシは、インターフェイスを実装するクラスのみをプロキシでき、インターフェイスを実装していない通常のクラスをプロキシすることはできません。
  2. 動的に生成される: プロキシ クラスは実行時に動的に生成されるため、開発者はプロキシ クラスのコードを手動で記述する必要はありません。
  3. 高い柔軟性: 元のクラス コードを変更せずに、追加の機能やロジックを元のクラスに追加できます。
  4. パフォーマンスに関する考慮事項注: リフレクションと動的クラス生成が関与しているため、JDK プロキシのパフォーマンスは、静的プロキシや元のクラスのメソッドへの直接呼び出しよりもわずかに低下する可能性があります。

2)CGLib

① CGLib ダイナミックプロキシの基本原理

  1. バイトコード操作 : CGLib は、内部で ASM (小型で高速なバイトコード操作フレームワーク) を使用して、新しい Java クラス (通常はターゲット クラスのサブクラス) を動的に生成します。これらの新しく生成されたクラスはターゲット クラスを継承し、メソッドが呼び出されるときにプロキシ ロジックを挿入します。
  2. メソッドインターセプト : CGLib の中心的な機能は、メソッドレベルのインターセプトを実装することです。開発者が実装するMethodInterceptorインターフェイスを使用してメソッド インターセプタを定義します。これにより、前処理、後処理、例外処理など、プロキシ オブジェクトのメソッド呼び出しの前後にカスタム ロジックが実行されます。
  3. FastClass メカニズム : パフォーマンスを向上させるために、CGLib は FastClass メカニズムを採用しています。 FastClass はターゲット クラスのメソッドにインデックスを付け、呼び出し時にインデックスを介してターゲット メソッドに直接アクセスします。このメソッドは Java リフレクションよりもはるかに高速です。

② CGLibダイナミックプロキシの実装手順

  • CGLib 依存関係の紹介:CGLib の Maven または Gradle 依存関係をプロジェクトに導入します。
  1. import net.sf.cglib.proxy.Enhancer;
  2. import net.sf.cglib.proxy.MethodInterceptor;
  3. import net.sf.cglib.proxy.MethodProxy;
  4. import java.lang.reflect.Method;
  • ターゲットクラスを定義する: プロキシする必要があるターゲット クラスを定義します。
  1. public class Target {
  2. public void method1() {
  3. System.out.println("method1 running ...");
  4. }
  5. public void method2() {
  6. System.out.println("method2 running ...");
  7. }
  8. public int method3(Integer i) {
  9. System.out.println("method3 running ...");
  10. return i;
  11. }
  12. }
  • MethodInterceptor インターフェースを実装する:実装を作成するMethodInterceptorインターフェースクラスとオーバーライドintercept方法。このメソッドにプロキシ ロジックを記述します。
  • プロキシオブジェクトの作成: CGLib が提供するものを使用します。Enhancerプロキシ オブジェクトを作成するクラス。プロキシクラスを設定する必要があります(経由)setSuperclassメソッド) とコールバック (経由)setCallbackメソッドは MethodInterceptor 実装クラスを設定します)。
  1. public class TargetProxy {
  2. public static <T> Object getProxy(T t) {
  3. Enhancer en = new Enhancer(); //帮我们生成代理对象
  4. en.setSuperclass(t.getClass());//设置要代理的目标类
  5. en.setCallback(new MethodInterceptor() {//代理要做什么
  6. @Override
  7. public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
  8. System.out.println("执行方法前。。。");
  9. //调用原有方法
  10. Object invoke = methodProxy.invokeSuper(object, args);
  11. // Object invoke = method.invoke(t, args);// 作用等同与上面。
  12. System.out.println("执行方法后。。。");
  13. return invoke;
  14. }
  15. });
  16. return en.create();
  17. }
  18. }
  • プロキシ オブジェクトを使用する: ターゲット クラスのメソッドがプロキシ オブジェクト経由で呼び出されたときにトリガーされます。interceptメソッド内のプロキシ ロジック。
  1. public class TargetUser {
  2. public static void main(String[] args) {
  3. Target target = (Target) TargetProxy.getProxy(new Target());
  4. System.out.println(target.getClass().getName());
  5. target.method1();
  6. }
  7. }

結果出力:

com.heaboy.aopdemo.cglibproxy.ターゲットh1つのerBええB翻訳元
メソッドを実行する前に。 。 。
method1 実行中...
メソッドの実行後。 。 。

③CGLibダイナミックプロキシの適用シナリオ

  1. インターフェイスを実装していないクラスをプロキシする必要がある: ターゲット クラスがインターフェイスを実装していない場合、CGLib をプロキシとして使用できます。
  2. 高いパフォーマンス要件: 高いパフォーマンス要件が必要なシナリオで、JDK 動的プロキシがニーズを満たすことができない場合は、CGLib の使用を検討できます。
  3. AOPフレームワークの実装: Spring AOP などのアスペクト指向プログラミング フレームワークでは、インターフェイスを実装していないクラスをプロキシする必要がある場合、通常、CGLib が基礎となる実装として使用されます。

④ CGLibダイナミックプロキシのメリット・デメリット

アドバンテージ:

  1. 高い柔軟性: インターフェイスを実装していないクラスをプロキシできるため、プロキシの適用範囲が広がります。
  2. よりよい性能: FastClass メカニズムにより、JDK ダイナミック プロキシのリフレクション メカニズムよりも呼び出し効率が高くなります。
  3. 強力な: 元のクラス コードを変更せずに、実行時に追加の機能またはロジックをターゲット クラスに動的に追加することをサポートします。

欠点:

  1. バイトコード操作のオーバーヘッド: バイトコードを動的に生成して JVM にロードすると、パフォーマンスに一定のオーバーヘッドが生じます。
  2. 最終クラスとメソッドをプロキシできません注:CGLibは対象クラスを継承してプロキシを実装しているため、最終的に変更されたクラスやメソッドをプロキシすることはできません。
  3. 使用がより複雑になる: JDK ダイナミック プロキシと比較すると、CGLib は使用が複雑であり、追加の依存関係を導入し、バイトコード生成の問題に対処する必要があります。

3)JDKプロキシとCGLib

プロキシ オブジェクト タイプ:JDK プロキシはインターフェイスを実装するクラスのみをプロキシできますが、CGLib は通常のクラスを直接プロキシできます。

パフォーマンス: CGLib は実行時にプロキシ クラスのサブクラスを生成し、一般に JDK プロキシよりもパフォーマンスがわずかに優れていると考えられています。ただし、ほとんどのシナリオでは、このパフォーマンスの違いは重要ではありません。

使用するシーン:ターゲット オブジェクトがすでにインターフェイスを実装している場合は、JDK プロキシを使用するのがシンプルで簡単な選択です。インターフェイスを実装していないクラスをプロキシする必要がある場合は、CGLib を使用する必要があります。

頼る:JDK プロキシは Java コア ライブラリの一部であるため追加の依存関係は必要ありませんが、CGLib ではプロジェクトの依存関係として CGLib ライブラリを追加する必要があります。

JDK プロキシは Java 言語に付属する機能であり、サードパーティのクラスをロードして実装する必要はありません。

Java は JDK プロキシの安定したサポートを提供し、JDK プロキシのアップグレードと更新を継続します。たとえば、Java 8 バージョンの JDK プロキシのパフォーマンスは以前のバージョンに比べて大幅に向上しています。

JDK プロキシはインターセプターとリフレクションを通じて実装されます。

JDK プロキシは、インターフェイスを継承するクラスのみをプロキシできます。

JDK プロキシの実装と呼び出しは比較的簡単です。

CGLib はサードパーティが提供するツールであり、ASM に基づいて実装されており、比較的高いパフォーマンスを持っています。

CGLib はインターフェイスを通じて実装する必要はなく、サブクラスを実装することによって呼び出されます。

4. 静的プロキシ VS 動的プロキシ

違い

静的プロキシ: 静的プロキシは、コンパイル時に決定されます。プロキシ クラスは、プロキシ クラスごとに作成する必要があります。プロキシ クラスとプロキシ クラスは、同じインターフェイスを実装するか、同じ親クラスを継承します。

静的プロキシのプロキシ クラスはコンパイル時に存在するため、プログラムの実行中に特定のクラスのみをプロキシでき、どのクラスをプロキシするかを動的に決定することはできません。

静的プロキシは元のオブジェクトのメソッド呼び出しをラップし、呼び出しの前後にロジックを追加できますが、プロキシ クラスを事前に記述する必要があるため、コード量が増加します。

静的プロキシはコード内でプロキシ オブジェクトを明示的に指定しており、比較的直感的に使用できますが、新しいプロキシ クラスを追加するには再コンパイルが必要です。

動的プロキシ: 動的プロキシは、事前にプロキシ クラスを作成しなくても、実行時にプロキシ オブジェクトを作成します。 Java のリフレクション メカニズムを使用して、プロキシ クラスとプロキシ オブジェクトを動的に生成します。

動的プロキシはインターフェースに基づいており、java.lang.reflect.Proxy クラスと java.lang.reflect.InvocationHandler インターフェースを通じて実装されます。

動的プロキシは、複数のインターフェイスのクラスをプロキシし、どのクラスをプロキシするかを動的に決定できます。実行時に、必要に応じてさまざまなオブジェクトに対してプロキシを生成できるため、柔軟性が高まります。

動的プロキシでは、プロキシ クラスごとに特定のプロキシ クラスを作成する必要がないため、より柔軟でコードが節約されます。

動的エージェントは、ロギング、トランザクション管理など、プロキシ オブジェクトのメソッド呼び出しの前後にカスタム ロジックを追加できます。 動的プロキシの欠点は、静的プロキシと比較して、実行時にプロキシ オブジェクトを生成する際に一定のパフォーマンス オーバーヘッドが必要になることです。

該当シーン

静的プロキシは、次のシナリオに適しています。

対象となるオブジェクト(プロキシオブジェクト)の数が限定されて決まっている場合、プロキシクラスを手動で記述することで静的プロキシを実装できます。静的プロキシはコンパイル時にプロキシ クラスを作成するため、実行時のパフォーマンスが向上します。

静的プロキシは、ターゲット オブジェクトをカプセル化し、元のコードを変更せずに機能を追加します。このため、静的プロキシは、ロギングやトランザクション管理などの横断的な問題によく使用されます。

動的プロキシは、次のシナリオに適しています。

ターゲット オブジェクトの数が不確実な場合、または事前に決定できない場合、動的プロキシを使用すると、より便利にプロキシ オブジェクトを生成できます。実行時にプロキシ クラスとプロキシ オブジェクトを生成し、複数のプロキシ クラスを手動で記述するという面倒な作業を回避します。

動的プロキシは、実行時にターゲット オブジェクトのプロキシ動作を追加、削除、または変更する柔軟性を提供します。これにより、動的プロキシは、AOP (アスペクト指向プログラミング) や RPC (リモート プロシージャ コール) などのアプリケーション シナリオでよく使用されます。

動的プロキシは実行時にリフレクション メカニズムを通じてプロキシ クラスとプロキシ オブジェクトを作成するため、そのパフォーマンスは静的プロキシよりもわずかに低下する可能性があることに注意してください。さらに、動的プロキシはインターフェイスを実装するターゲット オブジェクトのみをプロキシできますが、静的プロキシにはこの制限がありません。

要約すると、静的プロキシは、ターゲット オブジェクトの数が限定的かつ確実で、カプセル化や追加機能が必要なシナリオに適していますが、動的プロキシは、ターゲット オブジェクトの数が不確実であるか、事前に決定できないシナリオに適しています。また、プロキシの動作は柔軟に追加、削除、または変更する必要があります。特定のニーズと状況に基づいて、適切な代理店方法を選択してください。

5. SpringAOPでのプロキシ実装

1) SpringAOP の概要

AOP の理解について話す

Spring AOP は Spring フレームワークの重要なモジュールであり、アスペクト指向プログラミングの実装に使用されます。

対面プログラミング 、これは、プログラマーがカスタムの横断ポイントを通じてモジュール化し、複数のクラスに影響を与える動作を再利用可能なモジュールにカプセル化できるようにするプログラミング モデルです。例: たとえば、ログ出力の場合、AOP を使用しない場合は、ログ出力ステートメントをすべてのクラスとメソッドに配置する必要があります。ただし、AOP を使用すると、ログ出力ステートメントを再利用可能なモジュールにカプセル化し、それらを 1 つのモジュールに配置できます。クラス内では、クラスを使用するたびにログ出力が自動的に完了します。

アスペクト指向プログラミングの考え方では、関数は2種類に分けられます

  • 中心的事業: ログイン、登録、追加、削除、変更、問い合わせをすべてコア業務といいます

  • 周辺機能: ログとトランザクション管理は二次的な周辺サービスです。

アスペクト指向プログラミングでは、コア ビジネス機能と周辺機能が結合されずに独立して開発され、アスペクト機能とコア ビジネス機能が「織り込まれ」ます。これは AOP と呼ばれます。

AOPはビジネスに関係のないものを変換できますが、ただし、ビジネス モジュールによって一般的に呼び出されるロジックまたは責任 (トランザクション処理、ログ管理、権限制御など) についてはカプセル化されています。、簡単にシステム内の重複コードを減らすモジュール間の結合を減らす、そして将来の拡張性と保守性の向上に貢献

AOP には次の概念があります。

  • アスペクトJ: アスペクトは単なる概念であり、それに対応する特定のインターフェイスやクラスはありません。ジョイン ポイント、アドバイス、ポイントカットの総称です。

  • 参加ポイント : 接続ポイントとは、メソッド呼び出しや例外処理など、プログラム実行中のポイントを指します。 Spring AOP では、メソッドレベルのジョインポイントのみがサポートされます。

  • アドバイス : 通知、つまり、私たちが定義するアスペクトにおける横断的なロジックには、「前後」、「前」、「後」の 3 つのタイプがあります。多くの AOP 実装フレームワークでは、Advice は通常インターセプターとして機能し、結合ポイント周辺で処理されるリンクとして多くのインターセプターを含めることもできます。

  • ポイントカット: Pointcut、結合ポイントを照合するために使用されます。AspectJ に含まれる結合ポイントを Pointcut でフィルターする必要があります。

  • 導入 : 導入。アスペクトは、推奨されたオブジェクトが実際には実装していない追加のインターフェイスを実装することを宣言できます。たとえば、プロキシ オブジェクトを使用して、2 つのターゲット クラスをプロキシすることができます。

  • 織り : ウィービング、接続ポイント、カット ポイント、通知、アスペクトができましたが、それらをプログラムにどのように適用するのでしょうか?そうです、それはウィービングです。ポイントカットの指導の下で、通知ロジックがターゲット メソッドに挿入され、メソッドが呼び出されたときに通知ロジックが実行されるようになります。

  • AOP プロキシ : AOP プロキシは、AOP 実装フレームワークでアスペクト プロトコルを実装するオブジェクトを指します。 Spring AOP には、JDK 動的プロキシと CGLIB 動的プロキシという 2 種類のプロキシがあります。

  • 対象オブジェクト: ターゲット オブジェクトはプロキシされるオブジェクトです。

Spring AOP は JDK 動的プロキシと Cglib プロモーションに基づいて実装されます。両方のプロキシ メソッドはランタイム メソッドであるため、コンパイル時処理がありません。そのため、Spring は Java コードを通じて実装されます。

AOP はどのような問題を解決しますか?

複数のクラスまたはオブジェクトに散在するいくつかの一般的な動作 (ロギング、トランザクション管理、権限制御、インターフェイス電流制限、インターフェイス電源など) は、通常、次のように呼ばれます。 横断的な懸念 。これらの動作をすべてのクラスまたはオブジェクトで繰り返し実装すると、コードが冗長で複雑になり、保守が困難になります。

AOP は、横断的な問題 (ロギング、トランザクション管理、権限制御、インターフェース電流制限、インターフェース電力など) を、 核となるビジネス ロジック (核となる懸念事項、核となる懸念事項) 関心事の分離を達成するには、焦点から切り離します。

AOP アプリケーションのシナリオ

  • ロギング: ロギングの注釈をカスタマイズし、AOP を使用して、1 行のコードでロギングを実現します。

  • パフォーマンス統計: AOP を使用してターゲット メソッドの実行前後のメソッドの実行時間をカウントし、最適化と分析を容易にします。

  • トランザクション管理:@Transactional アノテーションにより、Spring は例外操作のロールバックなどのトランザクション管理を実行できるようになり、トランザクション管理ロジックの繰り返しが不要になります。@Transactionalアノテーションは AOP に基づいて実装されます。

  • アクセス許可の制御: AOP を使用して、ターゲット メソッドを実行する前にユーザーが必要なアクセス許可を持っているかどうかを判断し、持っている場合はターゲット メソッドを実行します。そうでない場合は実行されません。たとえば、SpringSecurity は@PreAuthorize コード行をコメント化することで、権限の検証をカスタマイズできます。

  • インターフェイス電流制限: AOP を使用して、ターゲット メソッドが実行される前に、特定の電流制限アルゴリズムと実装を通じてリクエストを制限します。

  • キャッシュ管理: AOP を使用して、ターゲット メソッドの実行前後にキャッシュを読み取り、更新します。

AOP の実装方法

AOP の一般的な実装方法には、動的プロキシ、バイトコード操作などが含まれます。

Spring AOP は動的プロキシに基づいています。プロキシされるオブジェクトが特定のインターフェイスを実装している場合、Spring AOP は動的プロキシを使用します。 JDK プロキシ、プロキシ オブジェクトを作成するには、インターフェイスを実装していないオブジェクトの場合、JDK プロキシを使用してプロキシを作成することはできません。 Cglib プロキシとして機能するプロキシされたオブジェクトのサブクラスを生成します。

2) JDK Proxy 動的プロキシに基づいて SpringAOP を実装する

① SpringAOPの設定

存存在するするspring-aop.xml構成ファイルで関連する Bean とアスペクトを構成する

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
  5. <bean id="target" class="com.xxhh.aopdemo.aop.Target"/>
  6. <bean id="targetAdvice" class="com.xxhh.aopdemo.aop.TargetAdvice"/>
  7. <bean id="targetProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
  8. <property name="target" ref="target"/> <!--被代理的类-->
  9. <property name="interceptorNames" value="targetAdvice"/> <!--如果用多种增强方式,value的值使用逗号(,)分割-->
  10. <property name="proxyTargetClass" value="false"/> <!--如果设置为true,则创建基于类的代理(使用CGLIB);如果设置为false,则创建基于接口的代理(使用JDK动态代理)。-->
  11. <property name="interfaces" value="com.xxhh.aopdemo.aop.TargetInteface"/> <!--target实现的接口-->
  12. </bean>
  13. </beans>

② 抽象インターフェースを定義する

  1. public interface TargetInteface {
  2. void method1();
  3. void method2();
  4. int method3(Integer i);
  5. }

③ プロキシクラスを定義する

  1. public class Target implements TargetInteface{
  2. /*
  3. * 需要增强的方法,连接点JoinPoint
  4. **/
  5. @Override
  6. public void method1() {
  7. System.out.println("method1 running ...");
  8. }
  9. @Override
  10. public void method2() {
  11. System.out.println("method2 running ...");
  12. }
  13. @Override
  14. public int method3(Integer i) {
  15. System.out.println("method3 running ...");
  16. return i;
  17. }
  18. }

④ プロキシクラスの定義(拡張メソッド)

  1. public class TargetAdvice implements MethodInterceptor, MethodBeforeAdvice, AfterReturningAdvice {
  2. /*
  3. * 通知/增强
  4. **/
  5. @Override
  6. public Object invoke(MethodInvocation methodInvocation) throws Throwable {
  7. System.out.println("前置环绕通知");
  8. Object proceed = methodInvocation.proceed();
  9. System.out.println("后置环绕通知");
  10. return proceed;
  11. }
  12. @Override
  13. public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
  14. System.out.println("后置返回通知");
  15. }
  16. @Override
  17. public void before(Method method, Object[] args, Object target) throws Throwable {
  18. System.out.println("前置通知");
  19. }
  20. }

⑤ テスト

  1. public class AopTest {
  2. public static void main(String[] args) {
  3. ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-aop.xml");
  4. TargetInteface targetProxy = (TargetInteface) appCtx.getBean("targetProxy");
  5. targetProxy.method1();
  6. }
  7. }

出力結果:

サラウンドフロント通知
事前通知
method1 実行中...
返品通知後の
サラウンドバック通知

3) CGLib 動的プロキシに基づいて SpringAOP を実装する

① SpringAOPの設定

存存在するするspring-confaop.xml構成ファイルで関連する Bean とアスペクトを構成する

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  7. <!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象-->
  8. <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
  9. <!--扫包-->
  10. <context:component-scan base-package="com.xxhh.aopdemo.annotationaop"/>
  11. </beans>

② プロキシクラスを定義する

  1. /*
  2. * 目标类
  3. **/
  4. public class Target {
  5. public void method1() {
  6. System.out.println("method1 running ...");
  7. }
  8. public void method2() {
  9. System.out.println("method2 running ...");
  10. }
  11. /*
  12. * 连接点JoinPoint
  13. **/
  14. public int method3(Integer i) {
  15. System.out.println("method3 running ...");
  16. // int i1 = 1 / i;
  17. return i;
  18. }
  19. }

③ プロキシクラス(アスペクトクラス)を定義する

  1. import org.aspectj.lang.ProceedingJoinPoint;
  2. /*
  3. * 切面类
  4. **/
  5. public class TargetAspect {
  6. /*
  7. * 前置通知
  8. **/
  9. public void before() {
  10. System.out.println("conf前置通知");
  11. }
  12. public void after() {
  13. System.out.println("conf后置通知");
  14. }
  15. public void afterReturning() {
  16. System.out.println("conf后置返回通知");
  17. }
  18. public void afterThrowing(Exception ex) throws Exception {
  19. // System.out.println("conf异常通知");
  20. // System.out.println(ex.getMessage());
  21. }
  22. public Object around(ProceedingJoinPoint pjp) throws Throwable {
  23. Object proceed = null;
  24. if (!"".equals("admin")) {
  25. System.out.println("conf环绕前置");
  26. proceed = pjp.proceed(pjp.getArgs());
  27. System.out.println("conf环绕后置");
  28. }
  29. return proceed;
  30. }
  31. }

④ テスト

  1. public class AopTest {
  2. public static void main(String[] args) {
  3. ApplicationContext appCtx = new ClassPathXmlApplicationContext("spring-confaop.xml");
  4. Target targetProxy = (Target) appCtx.getBean("target");
  5. System.out.println(targetProxy.method3(0));
  6. }
  7. }

出力結果:

会議の事前通知
confサラウンドプレフィックス
method3 実行中...
conf 復帰後の通知
confサラウンドポスト
conf投稿通知

4) アノテーション動的プロキシに基づいて SpringAOP を実装する

① SpringAOPの設定

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:context="http://www.springframework.org/schema/context"
  5. xmlns:aop="http://www.springframework.org/schema/aop"
  6. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
  7. <!--先开启cglib代理,开启 exposeProxy = true,暴露代理对象-->
  8. <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
  9. <!--扫包-->
  10. <context:component-scan base-package="com.xxhh.aopdemo.annotationaop"/>
  11. </beans>

② アノテーションを定義する

  1. @Target(ElementType.METHOD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. public @interface TestAnnotation{
  5. }

③アスペクトクラスの定義

  1. /*
  2. * 切面类
  3. **/
  4. @Aspect
  5. @Component
  6. public class AnnotationAspect {
  7. // 定义一个切点:所有被RequestMapping注解修饰的方法会织入advice
  8. @Pointcut("@annotation(TestAnnotation)")
  9. private void advicePointcut() {}
  10. /*
  11. * 前置通知
  12. **/
  13. @Before("advicePointcut()")
  14. public void before() {
  15. System.out.println("annotation前置通知");
  16. }
  17. @After("advicePointcut()")
  18. public void after() {
  19. System.out.println("annotation后置通知");
  20. }
  21. @AfterReturning(pointcut = "advicePointcut()")
  22. public void afterReturning() {
  23. System.out.println("annotation后置返回通知");
  24. }
  25. @AfterThrowing(pointcut = "advicePointcut()", throwing = "ex")
  26. public void afterThrowing(Exception ex) throws Exception {
  27. System.out.println("annotation异常通知");
  28. System.out.println(ex.getMessage());
  29. }
  30. @Around("advicePointcut()")
  31. public Object around(ProceedingJoinPoint pjp) throws Throwable {
  32. Object proceed = null;
  33. if (!"".equals("admin")) {
  34. System.out.println("annotation环绕前置");
  35. proceed = pjp.proceed(pjp.getArgs());
  36. System.out.println("annotation环绕后置");
  37. }
  38. return proceed;
  39. }
  40. }

④コントローラーがアノテーションを追加

  1. @Controller
  2. public class TestController {
  3. @RequestMapping("/test.do")
  4. @ResponseBody
  5. public String testController() {
  6. TestController o = (TestController) AopContext.currentProxy();
  7. o.test();
  8. // System.out.println("tewt");
  9. return "ok";
  10. }
  11. @TestAnnotation
  12. public void test() {
  13. System.out.println("test running");
  14. }
  15. }

⑤ テスト