天天看點

Spring AOP 實作原理

什麼是AOP

AOP(Aspect-OrientedProgramming,面向方面程式設計),可以說是OOP(Object-Oriented Programing,面向對象程式設計)的補充和完善。OOP引入封裝、繼承和多态性等概念來建立一種對象層次結構,用以模拟公共行為的一個集合。當我們需要為分散的對象引入公共行為的時候,OOP則顯得無能為力。也就是說,OOP允許你定義從上到下的關系,但并不适合定義從左到右的關系。例如日志功能。日志代碼往往水準地散布在所有對象層次中,而與它所散布到的對象的核心功能毫無關系。對于其他類型的代碼,如安全性、異常處理和透明的持續性也是如此。這種散布在各處的無關的代碼被稱為橫切(cross-cutting)代碼,在OOP設計中,它導緻了大量代碼的重複,而不利于各個子產品的重用。

而AOP技術則恰恰相反,它利用一種稱為“橫切”的技術,剖解開封裝的對象内部,并将那些影響了多個類的公共行為封裝到一個可重用子產品,并将其名為“Aspect”,即方面。所謂“方面”,簡單地說,就是将那些與業務無關,卻為業務子產品所共同調用的邏輯或責任封裝起來,便于減少系統的重複代碼,降低子產品間的耦合度,并有利于未來的可操作性和可維護性。AOP代表的是一個橫向的關系,如果說“對象”是一個空心的圓柱體,其中封裝的是對象的屬性和行為;那麼面向方面程式設計的方法,就仿佛一把利刃,将這些空心圓柱體剖開,以獲得其内部的消息。而剖開的切面,也就是所謂的“方面”了。然後它又以巧奪天功的妙手将這些剖開的切面複原,不留痕迹。

使用“橫切”技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處都基本相似。比如權限認證、日志、事務處理。Aop 的作用在于分離系統中的各種關注點,将核心關注點和橫切關注點分離開來。正如Avanade公司的進階方案構架師Adam Magee所說,AOP的核心思想就是“将應用程式中的商業邏輯同對其提供支援的通用服務進行分離。”

實作AOP的技術,主要分為兩大類:一是采用動态代理技術,利用截取消息的方式,對該消息進行裝飾,以取代原有對象行為的執行;二是采用靜态織入的方式,引入特定的文法建立“方面”,進而使得編譯器可以在編譯期間織入有關“方面”的代碼。

AOP使用場景

AOP用來封裝橫切關注點,具體可以在下面的場景中使用:

Authentication 權限

Caching 緩存

Context passing 内容傳遞

Error handling 錯誤處理

Lazy loading 懶加載

Debugging  調試

logging, tracing, profiling and monitoring 記錄跟蹤 優化 校準

Performance optimization 性能優化

Persistence  持久化

Resource pooling 資源池

Synchronization 同步

Transactions 事務

AOP相關概念

方面(Aspect):一個關注點的子產品化,這個關注點實作可能另外橫切多個對象。事務管理是J2EE應用中一個很好的橫切關注點例子。方面用Spring的 Advisor或攔截器實作。

連接配接點(Joinpoint): 程式執行過程中明确的點,如方法的調用或特定的異常被抛出。

通知(Advice): 在特定的連接配接點,AOP架構執行的動作。各種類型的通知包括“around”、“before”和“throws”通知。通知類型将在下面讨論。許多AOP架構包括Spring都是以攔截器做通知模型,維護一個“圍繞”連接配接點的攔截器鍊。Spring中定義了四個advice: BeforeAdvice, AfterAdvice, ThrowAdvice和DynamicIntroductionAdvice

切入點(Pointcut): 指定一個通知将被引發的一系列連接配接點的集合。AOP架構必須允許開發者指定切入點:例如,使用正規表達式。 Spring定義了Pointcut接口,用來組合MethodMatcher和ClassFilter,可以通過名字很清楚的了解, MethodMatcher是用來檢查目标類的方法是否可以被應用此通知,而ClassFilter是用來檢查Pointcut是否應該應用到目标類上

引入(Introduction): 添加方法或字段到被通知的類。 Spring允許引入新的接口到任何被通知的對象。例如,你可以使用一個引入使任何對象實作 IsModified接口,來簡化緩存。Spring中要使用Introduction, 可有通過DelegatingIntroductionInterceptor來實作通知,通過DefaultIntroductionAdvisor來配置Advice和代理類要實作的接口

目标對象(Target Object): 包含連接配接點的對象。也被稱作被通知或被代理對象。POJO

AOP代理(AOP Proxy): AOP架構建立的對象,包含通知。 在Spring中,AOP代理可以是JDK動态代理或者CGLIB代理。

織入(Weaving): 組裝方面來建立一個被通知對象。這可以在編譯時完成(例如使用AspectJ編譯器),也可以在運作時完成。Spring和其他純Java AOP架構一樣,在運作時完成織入。

Spring AOP元件

下面這種類圖列出了Spring中主要的AOP元件

如何使用Spring AOP

可以通過配置檔案或者程式設計的方式來使用Spring AOP。

配置可以通過xml檔案來進行,大概有四種方式:

1.        配置ProxyFactoryBean,顯式地設定advisors, advice, target等

2.        配置AutoProxyCreator,這種方式下,還是如以前一樣使用定義的bean,但是從容器中獲得的其實已經是代理對象

3.        通過<aop:config>來配置

4.        通過<aop: aspectj-autoproxy>來配置,使用AspectJ的注解來辨別通知及切入點

也可以直接使用ProxyFactory來以程式設計的方式使用Spring AOP,通過ProxyFactory提供的方法可以設定target對象, advisor等相關配置,最終通過 getProxy()方法來擷取代理對象

具體使用的示例可以google. 這裡略去

Spring AOP代理對象的生成

Spring提供了兩種方式來生成代理對象: JDKProxy和Cglib,具體使用哪種方式生成由AopProxyFactory根據AdvisedSupport對象的配置來決定。預設的政策是如果目标類是接口,則使用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()方法來具體看下Spring AOP是如何織入切面的。

public Object 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);  
               }  
           }  
        }             

主流程可以簡述為:擷取可以應用到此方法上的通知鍊(Interceptor Chain),如果有,則應用通知,并執行joinpoint; 如果沒有,則直接反射執行joinpoint。而這裡的關鍵是通知鍊是如何擷取的以及它又是如何執行的,下面逐一分析下。

首先,從上面的代碼可以看到,通知鍊是通過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);  
           }  
    }             
下一篇: javaUtil類