天天看點

【Spring】AOP的基本原理

Spring為Java大地帶來了一陣春風,它作為一個優秀的輕量級企業應用開發架構,能夠大大簡化企業應用開發的複雜性。

Spring以控制反轉(IOC)(從另個角度也叫:依賴注入(DI))和AOP這兩樣先進的設計理念為基礎,統一了應用對象的查找、配置和生命周期的管理,分離了業務與基礎服務中的不同關注

點,開發人員可以基于簡單Java對象輕松的實作與EJB同樣強大的功能。 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各種的實作

AOP就是面向切面程式設計,我們可以從幾個層面來實作AOP。

【Spring】AOP的基本原理

在編譯器修改源代碼,在運作期位元組碼加載前修改位元組碼或位元組碼加載後動态建立代理類的位元組碼,以下是各種實作機制的比較。 

類别 機制 原理 優點 缺點
靜态AOP 靜态織入 在編譯期,切面直接以位元組碼的形式編譯到目标位元組碼檔案中。 對系統無性能影響。 靈活性不夠。
動态AOP jdk動态代理 在運作期,目标類加載後,為接口動态生成代理類,将切面植入到代理類中。 相對于靜态AOP更加靈活。 切入的關注點需要實作接口。對系統有一點性能影響。
動态位元組碼生成(Cglib) 在運作期,目标類加載後,動态建構位元組碼檔案生成目标類的子類,将切面邏輯加入到子類中。 沒有接口也可以織入。 擴充類的執行個體方法為final時,則無法進行織入。
自定義類加載器 在運作期,目标加載前,将切面邏輯加到目标位元組碼裡。 可以對絕大部分類進行織入。 代碼中如果使用了其他類加載器,則這些類将不會被織入。
位元組碼轉換 在運作期,所有類加載器加載位元組碼前,前進行攔截。 可以對所有類進行織入。

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

可以通過配置檔案或者程式設計的方式來使用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這個類中,直接上相關代碼:

[java]  view plain copy

  1.    public Object getProxy(ClassLoader classLoader) {  
  2.        if (logger.isDebugEnabled()) {  
  3.            logger.debug("Creating JDK dynamic proxy: target source is " +this.advised.getTargetSource());  
  4.        }  
  5.        Class[] proxiedInterfaces =AopProxyUtils.completeProxiedInterfaces(this.advised);  
  6.        findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);  
  7.        return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);  
  8. }  

那這個其實很明了,注釋上我也已經寫清楚了,不再贅述。

下面的問題是,代理對象生成了,那切面是如何織入的?

我們知道InvocationHandler是JDK動态代理的核心,生成的代理對象的方法調用都會委托到InvocationHandler.invoke()方法。而通過JdkDynamicAopProxy的簽名我們可以看到這個類其實也實作了InvocationHandler,下面我們就通過分析這個類中實作的invoke()方法來具體看下Spring AOP是如何織入切面的。

[java]  view plain copy

  1. publicObject invoke(Object proxy, Method method, Object[] args) throwsThrowable {  
  2.        MethodInvocation invocation = null;  
  3.        Object oldProxy = null;  
  4.        boolean setProxyContext = false;  
  5.        TargetSource targetSource = this.advised.targetSource;  
  6.        Class targetClass = null;  
  7.        Object target = null;  
  8.        try {  
  9.            //eqauls()方法,具目标對象未實作此方法  
  10.            if (!this.equalsDefined && AopUtils.isEqualsMethod(method)){  
  11.                 return (equals(args[0])? Boolean.TRUE : Boolean.FALSE);  
  12.            }  
  13.            //hashCode()方法,具目标對象未實作此方法  
  14.            if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)){  
  15.                 return newInteger(hashCode());  
  16.            }  
  17.            //Advised接口或者其父接口中定義的方法,直接反射調用,不應用通知  
  18.            if (!this.advised.opaque &&method.getDeclaringClass().isInterface()  
  19.                     &&method.getDeclaringClass().isAssignableFrom(Advised.class)) {  
  20.                 // Service invocations onProxyConfig with the proxy config...  
  21.                 return AopUtils.invokeJoinpointUsingReflection(this.advised,method, args);  
  22.            }  
  23.            Object retVal = null;  
  24.            if (this.advised.exposeProxy) {  
  25.                 // Make invocation available ifnecessary.  
  26.                 oldProxy = AopContext.setCurrentProxy(proxy);  
  27.                 setProxyContext = true;  
  28.            }  
  29.            //獲得目标對象的類  
  30.            target = targetSource.getTarget();  
  31.            if (target != null) {  
  32.                 targetClass = target.getClass();  
  33.            }  
  34.            //擷取可以應用到此方法上的Interceptor清單  
  35.            List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,targetClass);  
  36.            //如果沒有可以應用到此方法的通知(Interceptor),此直接反射調用 method.invoke(target, args)  
  37.            if (chain.isEmpty()) {  
  38.                 retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);  
  39.            } else {  
  40.                 //建立MethodInvocation  
  41.                 invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);  
  42.                 retVal = invocation.proceed();  
  43.            }  
  44.            // Massage return value if necessary.  
  45.            if (retVal != null && retVal == target &&method.getReturnType().isInstance(proxy)  
  46.                     &&!RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {  
  47.                 // Special case: it returned"this" and the return type of the method  
  48.                 // is type-compatible. Notethat we can't help if the target sets  
  49.                 // a reference to itself inanother returned object.  
  50.                 retVal = proxy;  
  51.            }  
  52.            return retVal;  
  53.        } finally {  
  54.            if (target != null && !targetSource.isStatic()) {  
  55.                 // Must have come fromTargetSource.  
  56.                targetSource.releaseTarget(target);  
  57.            }  
  58.            if (setProxyContext) {  
  59.                 // Restore old proxy.  
  60.                 AopContext.setCurrentProxy(oldProxy);  
  61.            }  
  62.        }  
  63.     }  

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

首先,從上面的代碼可以看到,通知鍊是通過Advised.getInterceptorsAndDynamicInterceptionAdvice()這個方法來擷取的,我們來看下這個方法的實作:

[java]  view plain copy

  1. public List<Object>getInterceptorsAndDynamicInterceptionAdvice(Method method, Class targetClass) {  
  2.                    MethodCacheKeycacheKey = new MethodCacheKey(method);  
  3.                    List<Object>cached = this.methodCache.get(cacheKey);  
  4.                    if(cached == null) {  
  5.                             cached= this.advisorChainFactory.getInterceptorsAndDynamicInterceptionAdvice(  
  6.                                                this,method, targetClass);  
  7.                             this.methodCache.put(cacheKey,cached);  
  8.                    }  
  9.                    returncached;  
  10.          }  

可以看到實際的擷取工作其實是由AdvisorChainFactory. getInterceptorsAndDynamicInterceptionAdvice()這個方法來完成的,擷取到的結果會被緩存。

下面來分析下這個方法的實作:

[java]  view plain copy

  1.     publicList getInterceptorsAndDynamicInterceptionAdvice(Advised config, Methodmethod, Class targetClass) {  
  2.        // This is somewhat tricky... we have to process introductions first,  
  3.        // but we need to preserve order in the ultimate list.  
  4.        List interceptorList = new ArrayList(config.getAdvisors().length);  
  5.        //檢視是否包含IntroductionAdvisor  
  6.        boolean hasIntroductions = hasMatchingIntroductions(config,targetClass);  
  7.        //這裡實際上注冊一系列AdvisorAdapter,用于将Advisor轉化成MethodInterceptor  
  8.        AdvisorAdapterRegistry registry = GlobalAdvisorAdapterRegistry.getInstance();  
  9.        Advisor[] advisors = config.getAdvisors();  
  10.         for (int i = 0; i <advisors.length; i++) {  
  11.            Advisor advisor = advisors[i];  
  12.            if (advisor instanceof PointcutAdvisor) {  
  13.                 // Add it conditionally.  
  14.                 PointcutAdvisor pointcutAdvisor= (PointcutAdvisor) advisor;  
  15.                 if(config.isPreFiltered() ||pointcutAdvisor.getPointcut().getClassFilter().matches(targetClass)) {  
  16.                     //TODO: 這個地方這兩個方法的位置可以互換下  
  17.                     //将Advisor轉化成Interceptor  
  18.                     MethodInterceptor[]interceptors = registry.getInterceptors(advisor);  
  19.                     //檢查目前advisor的pointcut是否可以比對目前方法  
  20.                     MethodMatcher mm =pointcutAdvisor.getPointcut().getMethodMatcher();  
  21.                     if (MethodMatchers.matches(mm,method, targetClass, hasIntroductions)) {  
  22.                         if(mm.isRuntime()) {  
  23.                             // Creating a newobject instance in the getInterceptors() method  
  24.                             // isn't a problemas we normally cache created chains.  
  25.                             for (intj = 0; j < interceptors.length; j++) {  
  26.                                interceptorList.add(new InterceptorAndDynamicMethodMatcher(interceptors[j],mm));  
  27.                             }  
  28.                         } else {  
  29.                             interceptorList.addAll(Arrays.asList(interceptors));  
  30.                         }  
  31.                     }  
  32.                 }  
  33.            } else if (advisor instanceof IntroductionAdvisor){  
  34.                 IntroductionAdvisor ia =(IntroductionAdvisor) advisor;  
  35.                 if(config.isPreFiltered() || ia.getClassFilter().matches(targetClass)) {  
  36.                     Interceptor[] interceptors= registry.getInterceptors(advisor);  
  37.                     interceptorList.addAll(Arrays.asList(interceptors));  
  38.                 }  
  39.            } else {  
  40.                 Interceptor[] interceptors =registry.getInterceptors(advisor);  
  41.                 interceptorList.addAll(Arrays.asList(interceptors));  
  42.            }  
  43.        }  
  44.        return interceptorList;  
  45. }  

這個方法執行完成後,Advised中配置能夠應用到連接配接點或者目标類的Advisor全部被轉化成了MethodInterceptor.

接下來我們再看下得到的攔截器鍊是怎麼起作用的。

[java]  view plain copy

  1. if (chain.isEmpty()) {  
  2.                 retVal = AopUtils.invokeJoinpointUsingReflection(target,method, args);  
  3.             } else {  
  4.                 //建立MethodInvocation  
  5.                 invocation = newReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);  
  6.                 retVal = invocation.proceed();  
  7.             }  

         從這段代碼可以看出,如果得到的攔截器鍊為空,則直接反射調用目标方法,否則建立MethodInvocation,調用其proceed方法,觸發攔截器鍊的執行,來看下具體代碼

[java]  view plain copy

  1. public Object proceed() throws Throwable {  
  2.        //  We start with an index of -1and increment early.  
  3.        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size()- 1) {  
  4.            //如果Interceptor執行完了,則執行joinPoint  
  5.            return invokeJoinpoint();  
  6.        }  
  7.        Object interceptorOrInterceptionAdvice =  
  8.            this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);  
  9.        //如果要動态比對joinPoint  
  10.        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher){  
  11.            // Evaluate dynamic method matcher here: static part will already have  
  12.            // been evaluated and found to match.  
  13.            InterceptorAndDynamicMethodMatcher dm =  
  14.                 (InterceptorAndDynamicMethodMatcher)interceptorOrInterceptionAdvice;  
  15.            //動态比對:運作時參數是否滿足比對條件  
  16.            if (dm.methodMatcher.matches(this.method, this.targetClass,this.arguments)) {  
  17.                 //執行目前Intercetpor  
  18.                 returndm.interceptor.invoke(this);  
  19.            }  
  20.            else {  
  21.                 //動态比對失敗時,略過目前Intercetpor,調用下一個Interceptor  
  22.                 return proceed();  
  23.            }  
  24.        }  
  25.        else {  
  26.            // It's an interceptor, so we just invoke it: The pointcutwill have  
  27.            // been evaluated statically before this object was constructed.  
  28.            //執行目前Intercetpor  
  29.            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);  
  30.        }  
  31. }  

=======================================================================================

3 AOP的實作機制 

  本章節将詳細介紹AOP有各種實作機制。

3.1 動态代理 

  Java在JDK1.3後引入的動态代理機制,使我們可以在運作期動态的建立代理類。使用動态代理實作AOP需要有四個角色:被代理的類,被代理類的接口,織入器,和InvocationHandler,而織入器使用接口反射機制生成一個代理類,然後在這個代理類中織入代碼。被代理的類是AOP裡所說的目标,InvocationHandler是切面,它包含了Advice和Pointcut。 

【Spring】AOP的基本原理

3.1.1 使用動态代理 

  那如何使用動态代理來實作AOP。下面的例子示範在方法執行前織入一段記錄日志的代碼,其中Business是代理類,LogInvocationHandler是記錄日志的切面,IBusiness, IBusiness2是代理類的接口,Proxy.newProxyInstance是織入器。 

清單一:動态代理的示範

Java代碼  

【Spring】AOP的基本原理
  1. public static void main(String[] args) {   
  2.     //需要代理的接口,被代理類實作的多個接口都必須在這裡定義   
  3.     Class[] proxyInterface = new Class[] { IBusiness.class, IBusiness2.class };   
  4.     //建構AOP的Advice,這裡需要傳入業務類的執行個體   
  5.     LogInvocationHandler handler = new LogInvocationHandler(new Business());   
  6.     //生成代理類的位元組碼加載器   
  7.     ClassLoader classLoader = DynamicProxyDemo.class.getClassLoader();   
  8.     //織入器,織入代碼并生成代理類   
  9.     IBusiness2 proxyBusiness = (IBusiness2) Proxy.newProxyInstance(classLoader, proxyInterface, handler);   
  10.     //使用代理類的執行個體來調用方法。   
  11.     proxyBusiness.doSomeThing2();   
  12.     ((IBusiness) proxyBusiness).doSomeThing();   
  13. }   
  14. public static class LogInvocationHandler implements InvocationHandler {   
  15.     private Object target; //目标對象   
  16.     LogInvocationHandler(Object target) {   
  17.         this.target = target;   
  18.     }   
  19.     @Override   
  20.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {   
  21.         //執行原有邏輯   
  22.         Object rev = method.invoke(target, args);   
  23.         //執行織入的日志,你可以控制哪些方法執行切入邏輯   
  24.         if (method.getName().equals("doSomeThing2")) {   
  25.             System.out.println("記錄日志");   
  26.         }   
  27.         return rev;   
  28.     }   
  29. }   
  30. 接口IBusiness和IBusiness2定義省略。   

   業務類,需要代理的類。

Java代碼  

【Spring】AOP的基本原理
  1. public class Business implements IBusiness, IBusiness2 {   
  2.     @Override   
  3.     public boolean doSomeThing() {   
  4.         System.out.println("執行業務邏輯");   
  5.         return true;   
  6.     }   
  7.     @Override   
  8.     public void doSomeThing2() {   
  9.         System.out.println("執行業務邏輯2");   
  10.     }   
  11. }   

   輸出

Java代碼  

【Spring】AOP的基本原理
  1. 執行業務邏輯2   
  2. 記錄日志   
  3. 執行業務邏輯   

  可以看到“記錄日志”的邏輯切入到Business類的doSomeThing方法前了。

3.1.2 動态代理原理 

    本節将結合動态代理的源代碼講解其實作原理。動态代理的核心其實就是代理對象的生成,即Proxy.newProxyInstance(classLoader, proxyInterface, handler)。讓我們進入newProxyInstance方法觀摩下,核心代碼其實就三行。 

清單二:生成代理類

Java代碼  

【Spring】AOP的基本原理
  1. //擷取代理類   
  2. Class cl = getProxyClass(loader, interfaces);   
  3. //擷取帶有InvocationHandler參數的構造方法   
  4. Constructor cons = cl.getConstructor(constructorParams);   
  5. //把handler傳入構造方法生成執行個體   
  6. return (Object) cons.newInstance(new Object[] { h });     

    其中getProxyClass(loader, interfaces)方法用于擷取代理類,它主要做了三件事情:在目前類加載器的緩存裡搜尋是否有代理類,沒有則生成代理類并緩存在本地JVM裡。清單三:查找代理類。

Java代碼  

【Spring】AOP的基本原理
  1.  // 緩存的key使用接口名稱生成的List   
  2. Object key = Arrays.asList(interfaceNames);   
  3. synchronized (cache) {   
  4.     do {   
  5. Object value = cache.get(key);   
  6.          // 緩存裡儲存了代理類的引用   
  7. if (value instanceof Reference) {   
  8.     proxyClass = (Class) ((Reference) value).get();   
  9. }   
  10. if (proxyClass != null) {   
  11. // 代理類已經存在則傳回   
  12.     return proxyClass;   
  13. } else if (value == pendingGenerationMarker) {   
  14.     // 如果代理類正在産生,則等待   
  15.     try {   
  16. cache.wait();   
  17.     } catch (InterruptedException e) {   
  18.     }   
  19.     continue;   
  20. } else {   
  21.     //沒有代理類,則标記代理準備生成   
  22.     cache.put(key, pendingGenerationMarker);   
  23.     break;   
  24. }   
  25.     } while (true);   
  26. }   

代理類的生成主要是以下這兩行代碼。 清單四:生成并加載代理類

Java代碼  

【Spring】AOP的基本原理
  1. //生成代理類的位元組碼檔案并儲存到硬碟中(預設不儲存到硬碟)   
  2. proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);   
  3. //使用類加載器将位元組碼加載到記憶體中   
  4. proxyClass = defineClass0(loader, proxyName,proxyClassFile, 0, proxyClassFile.length);   

  ProxyGenerator.generateProxyClass()方法屬于sun.misc包下,Oracle并沒有提供源代碼,但是我們可以使用JD-GUI這樣的反編譯軟體打開jre\lib\rt.jar來一探究竟,以下是其核心代碼的分析。 

清單五:代理類的生成過程

Java代碼  

【Spring】AOP的基本原理
  1. //添加接口中定義的方法,此時方法體為空   
  2. for (int i = 0; i < this.interfaces.length; i++) {   
  3.   localObject1 = this.interfaces[i].getMethods();   
  4.   for (int k = 0; k < localObject1.length; k++) {   
  5.      addProxyMethod(localObject1[k], this.interfaces[i]);   
  6.   }   
  7. }   
  8. //添加一個帶有InvocationHandler的構造方法   
  9. MethodInfo localMethodInfo = new MethodInfo("<init>", "(Ljava/lang/reflect/InvocationHandler;)V", 1);   
  10. //循環生成方法體代碼(省略)   
  11. //方法體裡生成調用InvocationHandler的invoke方法代碼。(此處有所省略)   
  12. this.cp.getInterfaceMethodRef("InvocationHandler", "invoke", "Object; Method; Object;")   
  13. //将生成的位元組碼,寫入硬碟,前面有個if判斷,預設情況下不儲存到硬碟。   
  14. localFileOutputStream = new FileOutputStream(ProxyGenerator.access$000(this.val$name) + ".class");   
  15. localFileOutputStream.write(this.val$classFile);   

  那麼通過以上分析,我們可以推出動态代理為我們生成了一個這樣的代理類。把方法doSomeThing的方法體修改為調用LogInvocationHandler的invoke方法。 

清單六:生成的代理類源碼

Java代碼  

【Spring】AOP的基本原理
  1. public class ProxyBusiness implements IBusiness, IBusiness2 {   
  2. private LogInvocationHandler h;   
  3. @Override   
  4. public void doSomeThing2() {   
  5.     try {   
  6.         Method m = (h.target).getClass().getMethod("doSomeThing", null);   
  7.         h.invoke(this, m, null);   
  8.     } catch (Throwable e) {   
  9.         // 異常處理(略)   
  10.     }   
  11. }   
  12. @Override   
  13. public boolean doSomeThing() {   
  14.     try {   
  15.        Method m = (h.target).getClass().getMethod("doSomeThing2", null);   
  16.        return (Boolean) h.invoke(this, m, null);   
  17.     } catch (Throwable e) {   
  18.         // 異常處理(略)   
  19.     }   
  20.     return false;   
  21. }   
  22. public ProxyBusiness(LogInvocationHandler h) {   
  23.     this.h = h;   
  24. }   
  25. //測試用   
  26. public static void main(String[] args) {   
  27.     //建構AOP的Advice   
  28.     LogInvocationHandler handler = new LogInvocationHandler(new Business());   
  29.     new ProxyBusiness(handler).doSomeThing();   
  30.     new ProxyBusiness(handler).doSomeThing2();   
  31. }   
  32. }   

3.1.3 小結 

    從前兩節的分析我們可以看出,動态代理在運作期通過接口動态生成代理類,這為其帶來了一定的靈活性,但這個靈活性卻帶來了兩個問題,第一代理類必須實作一個接口,如果沒實作接口會抛出一個異常。第二性能影響,因為動态代理使用反射的機制實作的,首先反射肯定比直接調用要慢,經過測試大概每個代理類比靜态代理多出10幾毫秒的消耗。其次使用反射大量生成類檔案可能引起Full GC造成性能影響,因為位元組碼檔案加載後會存放在JVM運作時區的方法區(或者叫持久代)中,當方法區滿的時候,會引起Full GC,是以當你大量使用動态代理時,可以将持久代設定大一些,減少Full GC次數。 

3.2 動态位元組碼生成 

   使用動态位元組碼生成技術實作AOP原理是在運作期間目标位元組碼加載後,生成目标類的子類,将切面邏輯加入到子類中,是以使用Cglib實作AOP不需要基于接口。

【Spring】AOP的基本原理

    本節介紹如何使用Cglib來實作動态位元組碼技術。Cglib是一個強大的,高性能的Code生成類庫,它可以在運作期間擴充Java類和實作Java接口,它封裝了Asm,是以使用Cglib前需要引入Asm的jar。 清單七:使用CGLib實作AOP

Java代碼  

【Spring】AOP的基本原理
  1. public static void main(String[] args) {   
  2.         byteCodeGe();   
  3.     }   
  4.     public static void byteCodeGe() {   
  5.         //建立一個織入器   
  6.         Enhancer enhancer = new Enhancer();   
  7.         //設定父類   
  8.         enhancer.setSuperclass(Business.class);   
  9.         //設定需要織入的邏輯   
  10.         enhancer.setCallback(new LogIntercept());   
  11.         //使用織入器建立子類   
  12.         IBusiness2 newBusiness = (IBusiness2) enhancer.create();   
  13.         newBusiness.doSomeThing2();   
  14.     }   
  15.     public static class LogIntercept implements MethodInterceptor {   
  16.         @Override   
  17.         public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {   
  18.             //執行原有邏輯,注意這裡是invokeSuper   
  19.             Object rev = proxy.invokeSuper(target, args);   
  20.             //執行織入的日志   
  21.             if (method.getName().equals("doSomeThing2")) {   
  22.                 System.out.println("記錄日志");   
  23.             }   
  24.             return rev;   
  25.         }   
  26.     }   

3.3 自定義類加載器 

   如果我們實作了一個自定義類加載器,在類加載到JVM之前直接修改某些類的方法,并将切入邏輯織入到這個方法裡,然後将修改後的位元組碼檔案交給虛拟機運作,那豈不是更直接。

【Spring】AOP的基本原理

Javassist是一個編輯位元組碼的架構,可以讓你很簡單地操作位元組碼。它可以在運作期定義或修改Class。使用Javassist實作AOP的原理是在位元組碼加載前直接修改需要切入的方法。這比使用Cglib實作AOP更加高效,并且沒太多限制,實作原理如下圖: 

【Spring】AOP的基本原理

    我們使用系統類加載器啟動我們自定義的類加載器,在這個類加載器裡加一個類加載監聽器,監聽器發現目标類被加載時就織入切入邏輯,咱們再看看使用Javassist實作AOP的代碼: 

清單八:啟動自定義的類加載器

Java代碼  

【Spring】AOP的基本原理
  1. //擷取存放CtClass的容器ClassPool   
  2. ClassPool cp = ClassPool.getDefault();   
  3. //建立一個類加載器   
  4. Loader cl = new Loader();   
  5. //增加一個轉換器   
  6. cl.addTranslator(cp, new MyTranslator());   
  7. //啟動MyTranslator的main函數   
  8. cl.run("jsvassist.JavassistAopDemo$MyTranslator", args);   

 清單九:類加載監聽器

Java代碼  

【Spring】AOP的基本原理
  1. public static class MyTranslator implements Translator {   
  2.         public void start(ClassPool pool) throws NotFoundException, CannotCompileException {   
  3.         }   
  4.         public void onLoad(ClassPool pool, String classname) {   
  5.             if (!"model$Business".equals(classname)) {   
  6.                 return;   
  7.             }   
  8.             //通過擷取類檔案   
  9.             try {   
  10.                 CtClass  cc = pool.get(classname);   
  11.                 //獲得指定方法名的方法   
  12.                 CtMethod m = cc.getDeclaredMethod("doSomeThing");   
  13.                 //在方法執行前插入代碼   
  14.                 m.insertBefore("{ System.out.println(\"記錄日志\"); }");   
  15.             } catch (NotFoundException e) {   
  16.             } catch (CannotCompileException e) {   
  17.             }   
  18.         }   
  19.         public static void main(String[] args) {   
  20.             Business b = new Business();   
  21.             b.doSomeThing2();   
  22.             b.doSomeThing();   
  23.         }   
  24.     }   

 輸出: 

Java代碼  

【Spring】AOP的基本原理
  1. 執行業務邏輯2   
  2. 記錄日志   
  3. 執行業務邏輯  

    其中Bussiness類在本文的清單一中定義。看起來是不是特别簡單,CtClass是一個class檔案的抽象描述。咱們也可以使用insertAfter()在方法的末尾插入代碼,使用insertAt()在指定行插入代碼。 

3.3.1 小結 

    從本節中可知,使用自定義的類加載器實作AOP在性能上要優于動态代理和Cglib,因為它不會産生新類,但是它仍然存在一個問題,就是如果其他的類加載器來加載類的話,這些類将不會被攔截。 

3.4 位元組碼轉換 

    自定義的類加載器實作AOP隻能攔截自己加載的位元組碼,那麼有沒有一種方式能夠監控所有類加載器加載位元組碼呢?有,使用Instrumentation,它是 Java 5 提供的新特性,使用 Instrumentation,開發者可以建構一個位元組碼轉換器,在位元組碼加載前進行轉換。本節使用Instrumentation和javassist來實作AOP。 

3.4.1 建構位元組碼轉換器 

    首先需要建立位元組碼轉換器,該轉換器負責攔截Business類,并在Business類的doSomeThing方法前使用javassist加入記錄日志的代碼。

Java代碼  

【Spring】AOP的基本原理
  1. public class MyClassFileTransformer implements ClassFileTransformer {   
  2.     @Override   
  3.     public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,   
  4.                             ProtectionDomain protectionDomain, byte[] classfileBuffer)   
  5.             throws IllegalClassFormatException {   
  6.         System.out.println(className);   
  7.         //如果加載Business類才攔截   
  8.         if (!"model/Business".equals(className)) {   
  9.             return null;   
  10.         }   
  11.         //javassist的包名是用點分割的,需要轉換下   
  12.         if (className.indexOf("/") != -1) {   
  13.             className = className.replaceAll("/", ".");   
  14.         }   
  15.         try {   
  16.             //通過包名擷取類檔案   
  17.             CtClass cc = ClassPool.getDefault().get(className);   
  18.             //獲得指定方法名的方法   
  19.             CtMethod m = cc.getDeclaredMethod("doSomeThing");   
  20.             //在方法執行前插入代碼   
  21.             m.insertBefore("{ System.out.println(\"記錄日志\"); }");   
  22.             return cc.toBytecode();   
  23.         } catch (NotFoundException e) {   
  24.         } catch (CannotCompileException e) {   
  25.         } catch (IOException e) {   
  26.             //忽略異常處理   
  27.         }   
  28.         return null;   
  29. }   

3.4.2 注冊轉換器 

    使用premain函數注冊位元組碼轉換器,該方法在main函數之前執行。

Java代碼  

【Spring】AOP的基本原理
  1. public class MyClassFileTransformer implements ClassFileTransformer {   
  2.     public static void premain(String options, Instrumentation ins) {   
  3.         //注冊我自己的位元組碼轉換器   
  4.         ins.addTransformer(new MyClassFileTransformer());   
  5. }   
  6. }   

3.4.3 配置和執行 

    需要告訴JVM在啟動main函數之前,需要先執行premain函數。首先需要将premain函數所在的類打成jar包。并修改該jar包裡的META-INF\MANIFEST.MF 檔案。 

Java代碼  

【Spring】AOP的基本原理
  1. Manifest-Version: 1.0   
  2. Premain-Class: bci. MyClassFileTransformer  

     然後在JVM的啟動參數裡加上。-javaagent:D:\java\projects\opencometProject\Aop\lib\aop.jar 

             3.4.4 輸出

    執行main函數,你會發現切入的代碼無侵入性的織入進去了。

Java代碼  

【Spring】AOP的基本原理
  1. public static void main(String[] args) {   
  2.    new Business().doSomeThing();   
  3.    new Business().doSomeThing2();   
  4. }   

   輸出

Java代碼  

【Spring】AOP的基本原理
  1. model/Business   
  2. sun/misc/Cleaner   
  3. java/lang/Enum   
  4. model/IBusiness   
  5. model/IBusiness2   
  6. 記錄日志   
  7. 執行業務邏輯   
  8. 執行業務邏輯2   
  9. java/lang/Shutdown   
  10. java/lang/Shutdown$Lock   

 從輸出中可以看到系統類加載器加載的類也經過了這裡。

4 AOP實戰 

說了這麼多理論,那AOP到底能做什麼呢? AOP能做的事情非常多。

  • 性能監控,在方法調用前後記錄調用時間,方法執行太長或逾時報警。
  • 緩存代理,緩存某方法的傳回值,下次執行該方法時,直接從緩存裡擷取。
  • 軟體破解,使用AOP修改軟體的驗證類的判斷邏輯。
  • 記錄日志,在方法執行前後記錄系統日志。
  • 工作流系統,工作流系統需要将業務代碼和流程引擎代碼混合在一起執行,那麼我們可以使用AOP将其分離,并動态挂接業務。
  • 權限驗證,方法執行前驗證是否有權限執行目前方法,沒有則抛出沒有權限執行異常,由業務代碼捕捉。 

4.1 Spring的AOP 

   Spring預設采取的動态代理機制實作AOP,當動态代理不可用時(代理類無接口)會使用CGlib機制。但Spring的AOP有一定的缺點,第一個隻能對方法進行切入,不能對接口,字段,靜态代碼塊進行切入(切入接口的某個方法,則該接口下所有實作類的該方法将被切入)。第二個同類中的互相調用方法将不會使用代理類。因為要使用代理類必須從Spring容器中擷取Bean。第三個性能不是最好的,從3.3章節我們得知使用自定義類加載器,性能要優于動态代理和CGlib。 

可以擷取代理類

Java代碼  

【Spring】AOP的基本原理
  1. public IMsgFilterService getThis()   
  2. {   
  3.         return (IMsgFilterService) AopContext.currentProxy();   
  4. }   
  5. public boolean evaluateMsg () {   
  6.    // 執行此方法将織入切入邏輯   
  7. return getThis().evaluateMsg(String message);   
  8. }   
  9. @MethodInvokeTimesMonitor("KEY_FILTER_NUM")   
  10. public boolean evaluateMsg(String message) {   

 不能擷取代理類

Java代碼  

【Spring】AOP的基本原理
  1. public boolean evaluateMsg () {   
  2.    // 執行此方法将不會織入切入邏輯   
  3. return evaluateMsg(String message);   
  4. }   
  5. @MethodInvokeTimesMonitor("KEY_FILTER_NUM")   
  6. public boolean evaluateMsg(String message) {   

總結

一個典型的動态代理建立對象過程可分為以下四個步驟:

1、通過實作InvocationHandler接口建立自己的調用處理器 IvocationHandler handler = new InvocationHandlerImpl(...);

2、通過為Proxy類指定ClassLoader對象和一組interface建立動态代理類

Class clazz = Proxy.getProxyClass(classLoader,new Class[]{...});

3、通過反射機制擷取動态代理類的構造函數,其參數類型是調用處理器接口類型

Constructor constructor = clazz.getConstructor(new Class[]{InvocationHandler.class});

4、通過構造函數建立代理類執行個體,此時需将調用處理器對象作為參數被傳入

Interface Proxy = (Interface)constructor.newInstance(new Object[] (handler));

為了簡化對象建立過程,Proxy類中的newInstance方法封裝了2~4,隻需兩步即可完成代理對象的建立。

生成的ProxySubject繼承Proxy類實作Subject接口,實作的Subject的方法實際調用處理器的invoke方法,而invoke方法利用反射調用的是被代理對象的的方法(Object result=method.invoke(proxied,args))

美中不足

誠然,Proxy已經設計得非常優美,但是還是有一點點小小的遺憾之處,那就是它始終無法擺脫僅支援interface代理的桎梏,因為它的設計注定了這個遺憾。回想一下那些動态生成的代理類的繼承關系圖,它們已經注定有一個共同的父類叫Proxy。Java的繼承機制注定了這些動态代理類們無法實作對class的動态代理,原因是多繼承在Java中本質上就行不通。有很多條理由,人們可以否定對 class代理的必要性,但是同樣有一些理由,相信支援class動态代理會更美好。接口和類的劃分,本就不是很明顯,隻是到了Java中才變得如此的細化。如果隻從方法的聲明及是否被定義來考量,有一種兩者的混合體,它的名字叫抽象類。實作對抽象類的動态代理,相信也有其内在的價值。此外,還有一些曆史遺留的類,它們将因為沒有實作任何接口而從此與動态代理永世無緣。如此種種,不得不說是一個小小的遺憾。但是,不完美并不等于不偉大,偉大是一種本質,Java動态代理就是佐例。

繼續閱讀