この投稿では、Spring のアスペクト指向プログラミング (AOP) の内部メカニズムをわかりやすく説明します。多くの場合「魔法」の一種と考えられるロギングなどの機能を AOP がどのように実現するかを理解することに焦点を当てます。コア Java 実装を実際に見てみると、本当に魔法のようなものではなく、Java のリフレクション、プロキシ パターン、アノテーションがすべてであることがわかります。
これらはすべて、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; iMain.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仕組み
プロキシ オブジェクトでメソッドが呼び出されると、その呼び出しはハンドラーによってインターセプトされ、ハンドラーはリフレクションを使用してターゲット メソッドのすべてのアノテーションを検査します。これらのアノテーションに基づいて、ハンドラーはメソッドの入口/出口をログに記録するか、例外をログに記録するか、またはログを完全にスキップするかを決定します。
これを視覚化する方法は次のとおりです:
注釈の定義
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. * * @paramthe 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 createProxy(Class 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; } }
メソッドハンドラーの定義
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 が開発者の便宜のためにこれらの下位レベルの仕組みをどのように簡素化しているかをよりよく理解できるようになります。
免責事項: 提供されるすべてのリソースの一部はインターネットからのものです。お客様の著作権またはその他の権利および利益の侵害がある場合は、詳細な理由を説明し、著作権または権利および利益の証拠を提出して、電子メール [email protected] に送信してください。 できるだけ早く対応させていただきます。
Copyright© 2022 湘ICP备2022001581号-3