當前位置:
首頁 > 知識 > Spring AOP是什麼?你都拿它做什麼?

Spring AOP是什麼?你都拿它做什麼?

(給

ImportNew

加星標,提高Java技能)


轉自:我叫劉半仙,


https://my.oschina.net/liughDevelop/blog/1457097

為什麼會有面向切面編程(AOP)?我們知道Java是一個面向對象(OOP)的語言,但它有一些弊端,比如當我們需要為多個不具有繼承關係的對象引入一個公共行為,例如日誌、許可權驗證、事務等功能時,只能在在每個對象里引用公共行為。這樣做不便於維護,而且有大量重複代碼。AOP的出現彌補了OOP的這點不足。



為了闡述清楚Spring AOP,我們從將以下方面進行討論:





  1. 代理模式



  2. 靜態代理原理及實踐



  3. 動態代理原理及實踐



  4. Spring AOP原理及實戰



1. 代理模式



代理模式:為其他對象提供一種代理以控制對這個對象的訪問。這段話比較官方,但我更傾向於用自己的語言理解:比如A對象要做一件事情,在沒有代理前,自己來做;在對 A 代理後,由 A 的代理類 B 來做。代理其實是在原實例前後加了一層處理,這也是 AOP 的初級輪廓。



2. 靜態代理原理及實踐



靜態代理模式:靜態代理說白了,就是在程序運行前就已經存在代理類的位元組碼文件、代理類和原始類的關係在運行前就已經確定。廢話不多說,我們看一下代碼。為了方便閱讀,博主把單獨的 class 文件合併到介面中,讀者可以直接複製代碼運行:

package

test.staticProxy;

// 介面


public

interface

IUserDao

{
   

void

save

()

;
   

void

find

()

;
}

//目標對象


class

UserDao

implements

IUserDao

{
   

@Override

   

public

void

save

()

{
       System.out.println(

"模擬:保存用戶!"

);
   }
   

@Override


   

public

void

find

()

{
       System.out.println(

"模擬:查詢用戶"

);
   }
}

/**
 * 靜態代理
 * 特點:
 * 2. 目標對象必須要實現介面
 * 2. 代理對象,要實現與目標對象一樣的介面
*/


class

UserDaoProxy

implements

IUserDao

{

   

// 代理對象,需要維護一個目標對象

   

private

IUserDao target =

new

UserDao();

   

@Override


   

public

void

save

()

{
       System.out.println(

"代理操作: 開啟事務..."

);
       target.save();  

// 執行目標對象的方法

       System.out.println(

"代理操作:提交事務..."

);
   }

   

@Override


   

public

void

find

()

{
       target.find();
   }
}

測試結果:



靜態代理雖然保證了業務類只需關注邏輯本身,代理對象的一個介面只服務於一種類型的對象。如果要代理的方法很多,勢必要為每一種方法都進行代理。再者,如果增加一個方法,除了實現類需要實現這個方法外,所有的代理類也要實現此方法。增加了代碼的維護成本。那麼要如何解決呢?答案是使用動態代理。



3. 動態代理原理及實踐



動態代理模式:動態代理類的源碼是在程序運行期間,通過 JVM 反射等機制動態生成。代理類和委託類的關係是運行時才確定的。實例如下:


package

test.dynamicProxy;

import

java.lang.reflect.InvocationHandler;

import

java.lang.reflect.Method;

import

java.lang.reflect.Proxy;

// 介面


public

interface

IUserDao

{
   

void

save

()

;
   

void

find

()

;
}

//目標對象


class

UserDao

implements

IUserDao

{

   

@Override


   

public

void

save

()

{
       System.out.println(

"模擬: 保存用戶!"

);
   }

   

@Override


   

public

void

find

()

{
       System.out.println(

"查詢"

);
   }
}

/**
* 動態代理:
* 代理工廠,給多個目標對象生成代理對象!
*
*/


class

ProxyFactory

{

   

// 接收一個目標對象


   

private

Object target;

   

public

ProxyFactory

(Object target)

{
       

this

.target = target;
   }

   

// 返回對目標對象(target)代理後的對象(proxy)


   

public

Object

getProxyInstance

()

{
       Object proxy = Proxy.newProxyInstance(
           target.getClass().getClassLoader(),  

// 目標對象使用的類載入器


           target.getClass().getInterfaces(),  

// 目標對象實現的所有介面


           

new

InvocationHandler() {            

// 執行代理對象方法時候觸發

               

@Override


               

public

Object

invoke

(Object proxy, Method method, Object[] args)


                       

throws

Throwable

{

                   

// 獲取當前執行的方法的方法名


                   String methodName = method.getName();
                   

// 方法返回值


                   Object result =

null

;
                   

if

(

"find"

.equals(methodName)) {
                       

// 直接調用目標對象方法


                       result = method.invoke(target, args);
                   }

else

{
                       System.out.println(

"開啟事務..."

);
                       

// 執行目標對象方法


                       result = method.invoke(target, args);
                       System.out.println(

"提交事務..."

);
                   }
                   

return

result;
               }
           }
       );
       

return

proxy;
   }
}

測試結果如下:




IUserDao proxy = (IUserDao)

new

ProxyFactory(target).getProxyInstance();

其實是 JDK 動態生成了一個類去實現介面,隱藏了這個過程:

class

$

jdkProxy

implements

IUserDao

{}

使用 JDK 生成的動態代理的前提是目標類必須有實現的介面

。但這裡又引入一個問題,如果某個類沒有實現介面,就不能使用 JDK 動態代理。所以 CGLIB 代理就是解決這個問題的。



CGLIB 是以動態生成的子類繼承目標的方式實現,在運行期動態的在內存中構建一個子類,如下:

CGLIB 使用的前提是目標類不能為 final 修飾

。因為 final 修飾的類不能被繼承。



現在,我們可以看看 AOP 的定義:面向切面編程,核心原理是

使用動態代理模式在方法執行前後或出現異常時加入相關邏輯



通過定義和前面代碼我們可以發現3點:





  • AOP 是基於動態代理模式。



  • AOP 是方法級別的。



  • AOP 可以分離業務代碼和關注點代碼(重複代碼),在執行業務代碼時,動態的注入關注點代碼。切面就是關注點代碼形成的類。



4. Spring AOP



前文提到 JDK 代理和 CGLIB 代理兩種動態代理。優秀的 Spring 框架把兩種方式在底層都集成了進去,我們無需擔心自己去實現動態生成代理。那麼,Spring是如何生成代理對象的?





  1. 創建容器對象的時候,根據切入點表達式攔截的類,生成代理對象。



  2. 如果目標對象有實現介面,使用 JDK 代理。如果目標對象沒有實現介面,則使用 CGLIB 代理。然後從容器獲取代理後的對象,在運行期植入「切面」類的方法。通過查看 Spring 源碼,我們在 DefaultAopProxyFactory 類中,找到這樣一段話。

簡單的從字面意思看出:如果有介面,則使用 JDK 代理,反之使用 CGLIB ,這剛好印證了前文所闡述的內容。Spring AOP 綜合兩種代理方式的使用前提有會如下結論:如果目標類沒有實現介面,且 class 為 final 修飾的,則不能進行 Spring AOP 編程!



知道了原理,現在我們將自己手動實現 Spring 的 AOP:


package

test.spring_aop_anno;

import

org.aspectj.lang.ProceedingJoinPoint;

public

interface

IUserDao

{
   

void

save

()

;
}

// 用於測試 CGLIB 動態代理


class

OrderDao

{
   

public

void

save

()

{
       

//int i =1/0; 用於測試異常通知


       System.out.println(

"保存訂單..."

);
   }
}

//用於測試 JDK 動態代理


class

UserDao

implements

IUserDao

{
   

public

void

save

()

{
       

//int i =1/0; 用於測試異常通知


       System.out.println(

"保存用戶..."

);
   }
}

//切面類


class

TransactionAop

{

   

public

void

beginTransaction

()

{
       System.out.println(

"[前置通知]  開啟事務.."

);
   }

   

public

void

commit

()

{
       System.out.println(

"[後置通知] 提交事務.."

);
   }

   

public

void

afterReturing

()

{
       System.out.println(

"[返回後通知]"

);
   }

   

public

void

afterThrowing

()

{
       System.out.println(

"[異常通知]"

);
   }

   

public

void

arroud

(ProceedingJoinPoint pjp)

throws

Throwable

{
       System.out.println(

"[環繞前:]"

);
       pjp.proceed();

// 執行目標方法


       System.out.println(

"[環繞後:]"

);
   }
}

Spring 的 XML 配置文件:


<?

xml version=

"1.0"

encoding=

"UTF-8"

?>


<

beans

xmlns

=

"http://www.springframework.org/schema/beans"


   

xmlns:xsi

=

"http://www.w3.org/2001/XMLSchema-instance"


   

xmlns:context

=

"http://www.springframework.org/schema/context"


   

xmlns:aop

=

"http://www.springframework.org/schema/aop"


   

xsi:schemaLocation

=

"
       http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd"

>


   

<!-- dao實例加入容器 -->


   

<

bean

id

=

"userDao"

class

=

"test.spring_aop_anno.UserDao"

></

bean

>

   

<!-- dao實例加入容器 -->


   

<

bean

id

=

"orderDao"

class

=

"test.spring_aop_anno.OrderDao"

></

bean

>

   

<!-- 實例化切面類 -->


   

<

bean

id

=

"transactionAop"

class

=

"test.spring_aop_anno.TransactionAop"

></

bean

>

   

<!-- Aop相關配置 -->


   

<

aop:config

>


       

<!-- 切入點表達式定義 -->


       

<

aop:pointcut

expression

=

"execution(* test.spring_aop_anno.*Dao.*(..))"

id

=

"transactionPointcut"

/>


       

<!-- 切面配置 -->


       

<

aop:aspect

ref

=

"transactionAop"

>


           

<!-- 【環繞通知】 -->


           

<

aop:around

method

=

"arroud"

pointcut-ref

=

"transactionPointcut"

/>


           

<!-- 【前置通知】 在目標方法之前執行 -->


           

<

aop:before

method

=

"beginTransaction"

pointcut-ref

=

"transactionPointcut"

/>


           

<!-- 【後置通知】 -->


           

<

aop:after

method

=

"commit"

pointcut-ref

=

"transactionPointcut"

/>


           

<!-- 【返回後通知】 -->


           

<

aop:after-returning

method

=

"afterReturing"

pointcut-ref

=

"transactionPointcut"

/>


           

<!-- 異常通知 -->


           

<

aop:after-throwing

method

=

"afterThrowing"

pointcut-ref

=

"transactionPointcut"

/>


       

</

aop:aspect

>


   

</

aop:config

>


</

beans

>

切入點表達式不在這裡介紹。參考 

Spring AOP 切入點表達式

代碼的測試結果如下:



到這裡,我們已經全部介紹完Spring AOP。回到開篇的問題,我們拿它做什麼?





  1. Spring聲明式事務管理配置:請參考博主的另一篇文章:

    分散式系統架構實戰 demo:SSM+Dubbo



  2. Controller層的參數校驗:參考 

    Spring AOP攔截Controller做參數校驗



  3. 使用 Spring AOP 實現 MySQL 資料庫讀寫分離案例分析



  4. 在執行方法前,判斷是否具有許可權



  5. 對部分函數的調用進行日誌記錄:監控部分重要函數,若拋出指定的異常,可以以簡訊或郵件方式通知相關人員。



  6. 信息過濾,頁面轉發等等功能



博主一個人的力量有限,只能列舉這麼多,歡迎評論區對文章做補充。



Spring AOP還能做什麼,實現什麼魔幻功能,就在於我們每一個平凡而又睿智的程序猿!



參考文章





  • Spring AOP 切入點表達式:http://blog.csdn.net/keda8997110/article/details/50747923



  • 分散式系統架構實戰 demo:SSM+Dubbo:https://my.oschina.net/liughDevelop/blog/1480061



  • Spring AOP 攔截Controller做參數校驗:https://my.oschina.net/liughDevelop/blog/1480061



  • 使用 Spring AOP 實現 MySQL 資料庫讀寫分離案例分析:http://blog.csdn.net/xlgen157387/article/details/53930382



看完本文有收穫?請轉發分享給更多人


關注「ImportNew」,提升Java技能



喜歡就點「好看」唄~



喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 ImportNew 的精彩文章:

一份不能錯過的 Docker 實戰指南
CQRS - 簡單的架構設計

TAG:ImportNew |