Spring AOP の内部動作を明らかにする
この投稿では、Spring のアスペクト指向プログラミング (AOP) の内部メカニズムをわかりやすく説明します。多くの場合「魔法」の一種と考えられるロギングなどの機能を AOP がどのように実現するかを理解することに焦点を当てます。コア Java 実装を実際に見てみると、本当に魔法のようなものではなく、Java のリフレクション、プロキシ パターン、アノテーションがすべてであることがわかります。
前提条件
- Java コア プロキシ API
- リフレクション API
- アノテーション API
これらはすべて、java.lang.reflect、java.lang.annotation、および javassist.util.proxy パッケージの一部です。
コアメカニズム
Spring AOP の中心には、プロキシ オブジェクト、メソッド インターセプタ、およびリフレクションの概念があります。このパターンの中心となるのは、MethodHandler (または呼び出しハンドラー) です。このハンドラーは、メソッド呼び出しをインターセプトすることによって、プロキシ オブジェクトの動作を制御します。メソッドがプロキシ上で呼び出されると、そのメソッドはハンドラーを介して渡され、リフレクションを介してアノテーションをイントロスペクトできます。適用されたアノテーションに基づいて、必要なロジック (ログなど) を例外の前、後、または例外時に実行できます。
分解する
- プロキシ オブジェクト: これらは、実際のビジネス オブジェクトの代わりに動的に作成されるオブジェクトであり、メソッド ハンドラーを通じてメソッド呼び出しをルーティングします。
- 呼び出しハンドラー: ここでインターセプトの魔法が起こります。リフレクションを使用すると、ハンドラーはターゲット メソッドに存在するアノテーションを調べ、それに応じて動作を変更できます。
- カスタム アノテーション: ロギング、セキュリティ チェック、トランザクション管理などの追加機能をトリガーするマーカーとして機能するカスタム アノテーションを定義できます。
例: 特定のメソッドの実行の前後にログを追加するとします。あらゆる場所でログをハードコーディングする代わりに、メソッドに @BeforeMethod と @AfterMethod の注釈を付けることができます。私たちのハンドラーは、このアノテーションのメソッドを検査し、適切なロギング ロジックを動的に追加します。
以下は、この例のコントローラーとサービスがどのように見えるかを示すクラスです。
WorkerController.java
package edu.pk.poc.aop.controller; import edu.pk.poc.aop.annotation.AfterMethod; import edu.pk.poc.aop.annotation.All; import edu.pk.poc.aop.annotation.BeforeMethod; import edu.pk.poc.aop.helper.ProxyFactory; import edu.pk.poc.aop.service.Worker; import edu.pk.poc.aop.service.WorkerService; import edu.pk.poc.aop.service.WorkerServiceImpl; public class WorkerController { WorkerService workerService = ProxyFactory.createProxy(WorkerServiceImpl.class); /** * This Method 1s annotated with @BeforeMethod and @AfterMethod, So the log statements * will be generated before and after method call. */ @BeforeMethod @AfterMethod public void engageFullTimeWorker() throws Exception { Worker fullTimeWorker = new Worker(); fullTimeWorker.setName("FullTime-Worker"); fullTimeWorker.setPartTime(false); fullTimeWorker.setDuration(9); workerService.doWork(fullTimeWorker); } /** * This Method is annotated with @All, So the log statements will be generated before and after method call * along with exception if raised. */ @All public void engagePartTimeWorker() throws Exception { Worker partTimeWorker = new Worker(); partTimeWorker.setName("PartTime-Worker"); partTimeWorker.setPartTime(true); partTimeWorker.setDuration(4); workerService.doWork(partTimeWorker); } }
WorkerServiceImpl.java
package edu.pk.poc.aop.service; import edu.pk.poc.aop.annotation.AfterMethod; public class WorkerServiceImpl implements WorkerService { /** * Here this method is annotated with only @AfterMethod, So only log statement * will be generated after method call */ @AfterMethod @Override public void doWork(Worker worker) throws Exception { if (worker.isPartTime()) { throw new Exception("Part time workers are not permitted to work."); } System.out.print("A full time worker is working for " + worker.getDuration() + " hours :: "); for (int i = 1; i < worker.getDuration(); i++) { System.out.print("* "); } System.out.println(); } }
Main.java テスト クラス
package edu.pk.poc.aop.test; import edu.pk.poc.aop.controller.WorkerController; import edu.pk.poc.aop.helper.ProxyFactory; import edu.pk.util.Logger; public class Main { public static void main(String[] args) { WorkerController controller = ProxyFactory.createProxy(WorkerController.class); Logger logger = new Logger(); try { System.out.println("Testing @BeforeMethod and @AfterMethod"); System.out.println("-----------------------------------------"); controller.engageFullTimeWorker(); System.out.println("Testing @All"); System.out.println("-----------------------------------------"); controller.engagePartTimeWorker(); } catch (Exception e) { logger.error("Exception caught in Main class"); } } }
出力
Testing @BeforeMethod and @AfterMethod ----------------------------------------- >>> Entering into edu.pk.poc.aop.controller.WorkerController.engageFullTimeWorker() A full time worker is working for 9 hours :: * * * * * * * * >>> Exiting from edu.pk.poc.aop.service.WorkerServiceImpl.doWork() >>> Exiting from edu.pk.poc.aop.controller.WorkerController.engageFullTimeWorker() Testing @All ----------------------------------------- >>> Entering into edu.pk.poc.aop.controller.WorkerController.engagePartTimeWorker() >>> Exception in edu.pk.poc.aop.controller.WorkerController.engagePartTimeWorker() Exception caught in Main class
仕組み
プロキシ オブジェクトでメソッドが呼び出されると、その呼び出しはハンドラーによってインターセプトされ、ハンドラーはリフレクションを使用してターゲット メソッドのすべてのアノテーションを検査します。これらのアノテーションに基づいて、ハンドラーはメソッドの出入りをログに記録するか、例外をログに記録するか、またはログを完全にスキップするかを決定します。
これを視覚化する方法は次のとおりです:
- 実行前: メソッドのエントリをログに記録します。
- 実行後: メソッドの終了または成功をログに記録します。
- すべて: メソッド エントリ、メソッド エントリ、および例外が発生した場合はログに記録します。 この動的な動作は、Spring AOP が魔法のトリックを使用するのではなく、コア Java API を活用していることを示しています。
注釈の定義
package edu.pk.poc.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface AfterMethod { }
package edu.pk.poc.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface BeforeMethod { }
package edu.pk.poc.aop.annotation; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface All { }
プロキシ ファクトリを定義します
package edu.pk.poc.aop.helper; /** * The {@code ProxyFactory} class is responsible for creating proxy objects using the Javassist library. * It allows for dynamic generation of proxies for classes or interfaces, with support for method interception. */ public class ProxyFactory { /** * A Javassist ProxyFactory instance used to generate proxy classes. */ private static final javassist.util.proxy.ProxyFactory factory = new javassist.util.proxy.ProxyFactory(); /** * Creates a proxy object for the given class or interface. * If the class is an interface, the proxy implements the interface. * If it's a concrete class, the proxy extends the class. * * @param <T> the type of the class or interface for which the proxy is to be created * @param klass the {@code Class} object representing the class or interface to proxy * @return a proxy instance of the specified class or interface, or {@code null} if proxy creation fails */ public static <T> T createProxy(Class<T> klass) { if (klass.isInterface()) factory.setInterfaces(new Class[]{klass}); else factory.setSuperclass(klass); try { return (T) factory.create(new Class<?>[0], new Object[0], new AOPLoggingMethodHandler()); } catch (Exception e) { System.err.println(e.getMessage()); } return null; } }
MethodHandler を定義します
package edu.pk.poc.aop.helper; import edu.pk.poc.aop.annotation.AfterMethod; import edu.pk.poc.aop.annotation.All; import edu.pk.poc.aop.annotation.BeforeMethod; import edu.pk.poc.aop.annotation.OnException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import edu.pk.util.Logger; import javassist.util.proxy.MethodHandler; public class AOPLoggingMethodHandler implements MethodHandler { private static final Logger logger = new Logger(); public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { if (proceed != null) { // Concrete Method Object result = null; String className = resolveClassName(self); try { if (isAnnotationPresent(thisMethod, BeforeMethod.class) || isAnnotationPresent(thisMethod, All.class)) { logger.info(">>> Entering into " + className + "." + thisMethod.getName() + "()"); } result = proceed.invoke(self, args); if (isAnnotationPresent(thisMethod, AfterMethod.class) || isAnnotationPresent(thisMethod, All.class)) { logger.info(">>> Exiting from " + className + "." + thisMethod.getName() + "()"); } } catch (Throwable t) { if (isAnnotationPresent(thisMethod, OnException.class) || isAnnotationPresent(thisMethod, All.class)) { logger.error(">>> Exception in " + className + "." + thisMethod.getName() + "()"); } throw t; } return result; } throw new RuntimeException("Method is Abstract"); } private boolean isAnnotationPresent(Method method, Class klass) { Annotation[] declaredAnnotationsByType = method.getAnnotationsByType(klass); return declaredAnnotationsByType != null && declaredAnnotationsByType.length > 0; } private String resolveClassName(Object self) { String className = self.getClass().getName(); if (className.contains("_$$")) { className = className.substring(0, className.indexOf("_$$")); } return className; } }
結論
Spring AOP は横断的な問題に対応する強力なツールですが、何も革新的なことはしていません。これは、言語自体で使用できるリフレクションやプロキシなどの Java のコア概念に基づいて構築されています。これを理解することで、Spring が開発者の便宜のためにこれらの下位レベルの仕組みをどのように簡素化しているかをよりよく理解できるようになります。
以上がSpring AOP の内部動作を明らかにするの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

メモ帳++7.3.1
使いやすく無料のコードエディター

SublimeText3 中国語版
中国語版、とても使いやすい

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

ドリームウィーバー CS6
ビジュアル Web 開発ツール

SublimeText3 Mac版
神レベルのコード編集ソフト(SublimeText3)

ホットトピック











一部のアプリケーションが適切に機能しないようにする会社のセキュリティソフトウェアのトラブルシューティングとソリューション。多くの企業は、内部ネットワークセキュリティを確保するためにセキュリティソフトウェアを展開します。 ...

多くのアプリケーションシナリオでソートを実装するために名前を数値に変換するソリューションでは、ユーザーはグループ、特に1つでソートする必要がある場合があります...

システムドッキングでのフィールドマッピング処理は、システムドッキングを実行する際に難しい問題に遭遇することがよくあります。システムのインターフェイスフィールドを効果的にマッピングする方法A ...

intellijideaultimatiateバージョンを使用してスプリングを開始します...

データベース操作にMyBatis-Plusまたはその他のORMフレームワークを使用する場合、エンティティクラスの属性名に基づいてクエリ条件を構築する必要があることがよくあります。あなたが毎回手動で...

Javaオブジェクトと配列の変換:リスクの詳細な議論と鋳造タイプ変換の正しい方法多くのJava初心者は、オブジェクトのアレイへの変換に遭遇します...

eコマースプラットフォーム上のSKUおよびSPUテーブルの設計の詳細な説明この記事では、eコマースプラットフォームでのSKUとSPUのデータベース設計の問題、特にユーザー定義の販売を扱う方法について説明します。

Redisキャッシュソリューションは、製品ランキングリストの要件をどのように実現しますか?開発プロセス中に、多くの場合、ランキングの要件に対処する必要があります。
