Java-ソースコードからのSpringAOPの実現原理の分析

大きな牛の人工知能のチュートリアルを共有します。ゼロベース!わかりやすい!面白くてユーモラス!あなたも人工知能チームに参加してください!http://www.captainbed.netをクリックしてください

AOPとは

AOP(アスペクト指向プログラミング)は、OOP(オブジェクト指向プログラミング)の補完と改善であると言えます。OOPは、カプセル化、継承、ポリモーフィズムなどの概念を導入して、オブジェクトの階層を確立し、パブリック動作のコレクションをシミュレートします。散在するオブジェクトの公開動作を導入する必要がある場合、OOPは無力です。つまり、OOPを使用すると、上から下への関係を定義できますが、左から右への関係を定義するのには適していません。たとえば、ログ関数。ログコードは、多くの場合、すべてのオブジェクトレベルで水平方向に配布され、配布先のオブジェクトのコア機能とは関係ありません。同じことが、セキュリティ、例外処理、透過的永続性などの他のタイプのコードにも当てはまります。このような無関係なコードがいたるところに散らばっていると、横断的コードと呼ばれます。OOP設計では、コードの重複が多くなり、さまざまなモジュールの再利用につながりません。

AOPテクノロジーは正反対で、「クロスカッティング」と呼ばれるテクノロジーを使用して、カプセル化されたオブジェクトの内部を分析し、複数のクラスに影響を与える一般的な動作を再利用可能なモジュールにカプセル化して、名前を付けます。これが「アスペクト」です。 、つまり、アスペクト。いわゆる「アスペクト」は、簡単に言えば、ビジネスとは関係がないがビジネスモジュールによって呼び出されるロジックまたは責任をカプセル化することです。これは、システムの反復コードを減らし、間の結合を減らすのに便利です。モジュール、および将来の開発に役立ちます。操作性と保守性。AOPは水平方向の関係を表します。「オブジェクト」がオブジェクトのプロパティと動作をカプセル化する中空の円柱である場合、アスペクト指向プログラミング方法は鋭いエッジのようなもので、これらの中空の円柱を開いてニュースを内部に取り込みます。切断面は、いわゆる「切断面」です。その後、これらの切断面を巧妙なスキルで復元し、痕跡を残しませんでした。

AOPは、「横断的」テクノロジーを使用して、ソフトウェアシステムを2つの部分に分割します。コアの関心事と横断的関心事です。ビジネス処理の主な流れは中心的な関心事であり、関連性の低い部分は横断的関心事です。横断的関心事の特徴は、それらが中心的な関心事の複数の場所で頻繁に発生し、基本的にどこでも類似していることです。権限認証、ログ、トランザクション処理など。AOPの役割は、システム内のさまざまな懸念事項を分離し、主要な懸念事項を横断的関心事から分離することです。AOPの中心的なアイデアは、「アプリケーションのビジネスロジックをそれをサポートする一般的なサービスから分離する」ことです。

AOPを実現するテクノロジーは、主に2つのカテゴリに分類されます。1つは、メッセージをインターセプトしてメッセージを装飾し、元のオブジェクト動作の実行を置き換える動的プロキシテクノロジーを使用する方法です。もう1つは、静的ウィービングを使用して特定の構文を導入する方法です。コンパイラがコンパイル中に「アスペクト」に関するコードを織り込むことができるように、「アスペクト」を作成します。

AOPの使用シナリオ

AOPは、横断的関心事をカプセル化するために使用されます。これは、次のシナリオで使用できます。

認証権限

キャッシング

コンテキストの受け渡し

エラー処理

遅延読み込み

デバッグ

ロギング、トレース、プロファイリング、および監視

パフォーマンスの最適化

永続性

リソースプーリング

同期

トランザクション

AOP関連の概念

側面:フォーカスのモジュール性、このフォーカスの実現は、複数のオブジェクトにまたがる可能性があります。トランザクション管理は、J2EEアプリケーションにおける横断的関心事の良い例です。アスペクトは、Springのアドバイザまたはインターセプターを使用して実装されます。

ジョインポイント:メソッド呼び出しや特定の例外のスローなど、プログラム実行中の明確なポイント。

アドバイス:特定の接続ポイントでAOPフレームワークによって実行されるアクション。さまざまな種類の通知には、「周囲」、「前」、「スロー」の通知が含まれます。Springを含む多くのAOPフレームワークは、通知モデルとしてインターセプターを使用し、接続ポイントの「周囲」にインターセプターのチェーンを維持します。Springでは、BeforeAdvice、AfterAdvice、ThrowAdvice、DynamicIntroductionAdviceの4つのアドバイスが定義されています。

ポイントカット:通知がトリガーされる接続ポイントのコレクションを指定します。AOPフレームワークでは、開発者がエントリポイントを指定できるようにする必要があります。たとえば、正規表現を使用します。Springは、名前で明確に理解できるMethodMatcherとClassFilterを組み合わせるために使用されるPointcutインターフェイスを定義します。MethodMatcherは、ターゲットクラスのメソッドをこの通知に適用できるかどうかを確認するために使用され、ClassFilterは、ポイントカットは、上のターゲットクラスに適用する必要があります。

はじめに:通知されたクラスにメソッドまたはフィールドを追加します。Springでは、通知されたオブジェクトに新しいインターフェイスを導入できます。たとえば、インポートを使用して、任意のオブジェクトにIsModifiedインターフェイスを実装させ、キャッシュを簡素化できます。SpringでIntroductionを使用するには、DelegatingIntroductionInterceptorを介して通知を実装し、DefaultIntroductionAdvisorを介してAdviceおよびプロキシクラスによって実装されるようにインターフェイスを構成できます。

ターゲットオブジェクト:接続ポイントを含むオブジェクト。通知オブジェクトまたはプロキシオブジェクトとも呼ばれます。

AOPプロキシ:通知を含む、AOPフレームワークによって作成されたオブジェクト。Springでは、AOPプロキシはJDK動的プロキシまたはCGLIBプロキシにすることができます。

ウィービング:アスペクトを組み立てて、通知されたオブジェクトを作成します。これは、コンパイル時に(たとえば、AspectJコンパイラを使用して)実行することも、実行時に実行することもできます。Springは、他の純粋なJava AOPフレームワークと同様に、実行時にウィービングを完了します。

SpringAOPコンポーネント

次のクラス図は、Springの主なAOPコンポーネントを示しています。

SpringAOPの使用方法

Spring AOPは、構成ファイルを介して、またはプログラムで使用できます。

構成はxmlファイルを介して行うことができます。約4つの方法があります。

  1. アドバイザー、アドバイス、ターゲットなどを明示的に設定するようにProxyFactoryBeanを構成します。
  2. AutoProxyCreatorを構成します。この方法でも、以前と同じように定義済みのBeanを使用しますが、コンテナーから取得するのは実際にはプロキシオブジェクトです。
  3. <aop:config>を介して構成します。
  4. <aop:aspectj-autoproxy>を介して構成し、AspectJアノテーションを使用して通知とエントリポイントを識別します。

ProxyFactoryを直接使用してSpringAOPをプログラムで使用することもできます。ProxyFactoryが提供するメソッドを使用して、ターゲットオブジェクト、アドバイザ、およびその他の関連構成を設定し、最後にgetProxy()メソッドを使用してプロキシオブジェクトを取得できます。

SpringAOPプロキシオブジェクトの生成

Springには、プロキシオブジェクトを生成する2つの方法があります。JDKProxyとCglibです。生成に使用されるメソッドは、AdvisedSupportオブジェクトの構成に従ってAopProxyFactoryによって決定されます。デフォルトの戦略は、ターゲットクラスがインターフェイスの場合はJDK動的プロキシテクノロジを使用することです。それ以外の場合は、Cglibを使用してプロキシを生成します。SpringがJDKを使用してプロキシオブジェクトを生成する方法を調べてみましょう。生成された特定のコードはJdkDynamicAopProxyクラスに配置されます。関連するコードは次のとおりです。

/**
    * <ol>
    * <li>获取代理类要实现的接口,除了Advised对象中配置的,还会加上SpringProxy, Advised(opaque=false)
    * <li>检查上面得到的接口中有没有定义 equals或者hashcode的接口
    * <li>调用Proxy.newProxyInstance创建代理对象
    * </ol>
    */
   public Object getProxy(ClassLoader classLoader) {
       if (logger.isDebugEnabled()) {
           logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());
       }
       Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);
       findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
       return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

プロキシオブジェクトが生成されますが、アスペクトはどのように織り込まれていますか?

InvocationHandlerはJDK動的プロキシのコアであり、生成されたプロキシオブジェクトのメソッド呼び出しはInvocationHandler.invoke()メソッドに委任されることがわかっています。そして、JdkDynamicAopProxyのシグニチャを通じて、このクラスが実際にInvocationHandlerを実装していることがわかります。次に、このクラスに実装されているinvoke()メソッドを分析して、SpringAOPがアスペクトにどのように組み込まれているかを確認します。

publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {
       MethodInvocation invocation = null;
       Object oldProxy = null;
       boolean setProxyContext = false;
 
       TargetSource targetSource = this.advised.targetSource;
       Class targetClass = null;
       Object target = null;
 
       try {
           //eqauls()方法,具目标对象未实现此方法
           if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){
                return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);
           }
 
           //hashCode()方法,具目标对象未实现此方法
           if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){
                return newInteger(hashCode());
           }
 
           //Advised接口或者其父接口中定义的方法,直接反射调用,不应用通知
           if (!this.advised.opaque &&method.getDeclaringClass().isInterface()
                    &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {
                // Service invocations onProxyConfig with the proxy config...
                return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);
           }
 
           Object retVal = null;
 
           if (this.advised.exposeProxy) {
                // Make invocation available ifnecessary.
                oldProxy = AopContext.setCurrentProxy(proxy);
                setProxyContext = true;
           }
 
           //获得目标对象的类
           target = targetSource.getTarget();
           if (target != null) {
                targetClass = target.getClass();
           }
 
           //获取可以应用到此方法上的Interceptor列表
           List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);
 
           //如果没有可以应用到此方法的通知(Interceptor),此直接反射调用 method.invoke(target, args)
           if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
           } else {
                //创建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
           }
 
           // Massage return value if necessary.
           if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)
                    &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
                // Special case: it returned"this" and the return type of the method
                // is type-compatible. Notethat we can't help if the target sets
                // a reference to itself inanother returned object.
                retVal = proxy;
           }
           return retVal;
       } finally {
           if (target != null && !targetSource.isStatic()) {
                // Must have come fromTargetSource.
               targetSource.releaseTarget(target);
           }
           if (setProxyContext) {
                // Restore old proxy.
                AopContext.setCurrentProxy(oldProxy);
           }
       }
    }

主なプロセスは次のように簡単に説明できます。このメソッドに適用できるインターセプターチェーンを取得し、存在する場合は通知を適用し、ジョインポイントを実行します。そうでない場合は、ジョインポイントの実行を直接反映します。ここで重要なのは、通知チェーンの取得方法と実行方法です。これらは、以下で1つずつ分析されます。

まず、上記のコードからわかるように、通知チェーンはAdvised.getInterceptorsAndDynamicInterceptionAdvice()メソッドを介して取得されます。このメソッドの実装を見てみましょう。

public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {
                   MethodCacheKeycacheKey = new MethodCacheKey(method);
                   List<Object>cached = this.methodCache.get(cacheKey);
                   if(cached == null) {
                            cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(
                                               this,method, targetClass);
                            this.methodCache.put(cacheKey,cached);
                   }
                   returncached;
         }

実際のフェッチ作業はAdvisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice()メソッドによって実際に行われ、フェッチされた結果がキャッシュされることがわかります。

このメソッドの実装を分析してみましょう。

/**
    * 从提供的配置实例config中获取advisor列表,遍历处理这些advisor.如果是IntroductionAdvisor,
    * 则判断此Advisor能否应用到目标类targetClass上.如果是PointcutAdvisor,则判断
    * 此Advisor能否应用到目标方法method上.将满足条件的Advisor通过AdvisorAdaptor转化成Interceptor列表返回.
    */
    publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {
       // This is somewhat tricky... we have to process introductions first,
       // but we need to preserve order in the ultimate list.
       List interceptorList = new ArrayList(config.getAdvisors().length);
 
       //查看是否包含IntroductionAdvisor
       boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);
 
       //这里实际上注册一系列AdvisorAdapter,用于将Advisor转化成MethodInterceptor
       AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();
 
       Advisor[] advisors = config.getAdvisors();
        for (int i = 0; i <advisors.length; i++) {
           Advisor advisor = advisors[i];
           if (advisor instanceof PointcutAdvisor) {
                // Add it conditionally.
                PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;
                if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {
                    //TODO: 这个地方这两个方法的位置可以互换下
                    //将Advisor转化成Interceptor
                    MethodInterceptor[]interceptors = registry.getInterceptors(advisor);
 
                    //检查当前advisor的pointcut是否可以匹配当前方法
                    MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();
 
                    if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {
                        if(mm.isRuntime()) {
                            // Creating a newobject instance in the getInterceptors() method
                            // isn't a problemas we normally cache created chains.
                            for (intj = 0; j < interceptors.length; j++) {
                               interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));
                            }
                        } else {
                            interceptorList.addAll(Arrays.asList(interceptors));
                        }
                    }
                }
           } else if (advisor instanceof IntroductionAdvisor){
                IntroductionAdvisor ia =(IntroductionAdvisor) advisor;
                if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {
                    Interceptor[] interceptors= registry.getInterceptors(advisor);
                    interceptorList.addAll(Arrays.asList(interceptors));
                }
           } else {
                Interceptor[] interceptors =registry.getInterceptors(advisor);
                interceptorList.addAll(Arrays.asList(interceptors));
           }
       }
       return interceptorList;
}

このメソッドの実行が完了すると、接続ポイントまたはターゲットクラスに適用できるAdvisedで構成されたAdvisorがすべてMethodInterceptorに変換されます。

次に、取得したインターセプターチェーンがどのように機能するかを見てみましょう。

if (chain.isEmpty()) {
                retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);
            } else {
                //创建MethodInvocation
                invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
                retVal = invocation.proceed();
            }

このコードから、取得したインターセプターチェーンが空の場合、ターゲットメソッドがリフレクションによって直接呼び出されることがわかります。それ以外の場合は、MethodInvocationが作成され、そのproceedメソッドが呼び出され、インターセプターチェーンの実行がトリガーされます。特定のコード:

public Object proceed() throws Throwable {
       //  We start with an index of -1and increment early.
       if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {
           //如果Interceptor执行完了,则执行joinPoint
           return invokeJoinpoint();
       }
 
       Object interceptorOrInterceptionAdvice =
           this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
       
       //如果要动态匹配joinPoint
       if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){
           // Evaluate dynamic method matcher here: static part will already have
           // been evaluated and found to match.
           InterceptorAndDynamicMethodMatcher dm =
                (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;
           //动态匹配:运行时参数是否满足匹配条件
           if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {
                //执行当前Intercetpor
                returndm.interceptor.invoke(this);
           }
           else {
                //动态匹配失败时,略过当前Intercetpor,调用下一个Interceptor
                return proceed();
           }
       }
       else {
           // It's an interceptor, so we just invoke it: The pointcutwill have
           // been evaluated statically before this object was constructed.
           //执行当前Intercetpor
           return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
       }
}

これは、インターセプターチェーンの実行プロセスです。

おすすめ

転載: blog.csdn.net/chimomo/article/details/110875796