(給
ImportNew
加星標,提高Java技能)
轉自:我叫劉半仙,
https://my.oschina.net/liughDevelop/blog/1457097
為什麼會有面向切面編程(AOP)?我們知道Java是一個面向對象(OOP)的語言,但它有一些弊端,比如當我們需要為多個不具有繼承關係的對象引入一個公共行為,例如日誌、許可權驗證、事務等功能時,只能在在每個對象里引用公共行為。這樣做不便於維護,而且有大量重複代碼。AOP的出現彌補了OOP的這點不足。
為了闡述清楚Spring AOP,我們從將以下方面進行討論:
-
代理模式
-
靜態代理原理及實踐
-
動態代理原理及實踐
-
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是如何生成代理對象的?
-
創建容器對象的時候,根據切入點表達式攔截的類,生成代理對象。
-
如果目標對象有實現介面,使用 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”
>
<
bean
id
=
“userDao”
class
=
“test.spring_aop_anno.UserDao”
>
bean
>
<
bean
id
=
“orderDao”
class
=
“test.spring_aop_anno.OrderDao”
>
bean
>
<
bean
id
=
“transactionAop”
class
=
“test.spring_aop_anno.TransactionAop”
>
bean
>
<
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。回到開篇的問題,我們拿它做什麼?
-
Spring聲明式事務管理配置:請參考博主的另一篇文章:
分散式系統架構實戰 demo:SSM+Dubbo
-
Controller層的參數校驗:參考
Spring AOP攔截Controller做參數校驗
-
使用 Spring AOP 實現 MySQL 資料庫讀寫分離案例分析
-
在執行方法前,判斷是否具有許可權
-
對部分函數的調用進行日誌記錄:監控部分重要函數,若拋出指定的異常,可以以簡訊或郵件方式通知相關人員。
-
信息過濾,頁面轉發等等功能
博主一個人的力量有限,只能列舉這麼多,歡迎評論區對文章做補充。
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技能
喜歡就點「好看」唄~