本文為大家講解代理模式奠货,包括靜態(tài)代理的作用和代碼實(shí)現(xiàn)、動態(tài)代理的作用已慢、使用反射實(shí)現(xiàn)動態(tài)代理的過程曲聂,從而理解 AOP 的原理。
代理模式分為:靜態(tài)代理和動態(tài)代理佑惠。代理模式實(shí)現(xiàn)的功能和我們生活中的代理一樣朋腋,類似于中介公司。也就是代理對象幫助被代理對象完成功能膜楷,被代理對象可以在代理對象已有的功能基礎(chǔ)上旭咽,擴(kuò)展代理對象的功能。
比如在已存在的多個具有相同接口的目標(biāo)類的各個方法上增加一些系統(tǒng)功能赌厅,經(jīng)常會使用到代理模式穷绵,例如:方法執(zhí)行前后的日志打印,計算方法的運(yùn)行時間特愿、異常處理仲墨、事務(wù)等等勾缭。
一、靜態(tài)代理
靜態(tài)代理的介紹與代碼實(shí)現(xiàn)
下面我們通過一個例子來認(rèn)識靜態(tài)代理的作用目养,假設(shè)我們有一個操作數(shù)據(jù)庫的 Dao 服務(wù)接口 DaoService 俩由,里面包括查詢方法 query() 和更新方法 update(),程序中需要操作數(shù)據(jù)庫的有訂單服務(wù) OrderService 類癌蚁,它實(shí)現(xiàn)了 DaoService 接口幻梯。
我們思考一個問題,如果需要在查詢方法 query() 和更新方法 update() 執(zhí)行的前后需要加入日志打印功能努释,也就是說在方法執(zhí)行前打印一行日志碘梢,表示方法開始執(zhí)行,在方法執(zhí)行后打印一行日志伐蒂,表示方法執(zhí)行完畢煞躬。
大家可以思考,以上的問題我們可以怎么解決饿自?
任何人都能想到的最直接的辦法就是汰翠,在每個實(shí)現(xiàn)了 DaoService 接口的類的 query() 方法和 update() 方法的前后加上兩行日志就可以了。這種方法其實(shí)產(chǎn)生了很多的重復(fù)代碼昭雌,不是一個好的解決辦法复唤。如果實(shí)現(xiàn)了 DaoService 接口的類有很多,那么我們就會在這些類的 query() 方法和 update() 方法加很多日志代碼烛卧。
另外一個解決辦法就是用靜態(tài)代理來實(shí)現(xiàn)佛纫,用一個代理類 DaoServiceProxy 實(shí)現(xiàn) DaoService 接口,讓它來對實(shí)現(xiàn)了 DaoService 接口的類進(jìn)行代理总放,在代理類的query() 方法和 update() 方法的前后加上兩行日志就可以了呈宇。這樣做的好處是不管有多少 DaoService 的實(shí)現(xiàn)類,只需要加一個代理類局雄,在代理類的具體方法上加兩行日志就可以了甥啄,重復(fù)代碼得到了極大的減少。
示例代碼如下:
// DaoService接口
interface DaoService {
void query();
void update();
}
// 被代理類OrderService
class OrderService implements DaoService {
@Override
public void query() {
System.out.println("OrderService.query()");
}
@Override
public void update() {
System.out.println("OrderService.update()");
}
}
// 代理類
class DaoServiceProxy implements DaoService {
DaoService dao;
// 創(chuàng)建代理類對象的時候炬搭,實(shí)際上傳入一個被代理類的對象
public DaoServiceProxy(DaoService dao) {
this.dao = dao;
}
@Override
public void query() {
System.out.println("query()開始執(zhí)行蜈漓!");
dao.query();
System.out.println("query()執(zhí)行完畢!");
}
@Override
public void update() {
System.out.println("update()開始執(zhí)行宫盔!");
dao.update();
System.out.println("update()執(zhí)行完畢融虽!");
}
}
// 測試
public class TestStaticProxy1 {
public static void main(String[] args) {
// 創(chuàng)建被代理類的對象
OrderService orderService = new OrderService();
// 創(chuàng)建代理類的對象
DaoServiceProxy orderServiceProxy = new DaoServiceProxy(orderService);
orderServiceProxy.query();
System.out.println("==================");
orderServiceProxy.update();
}
}
運(yùn)行結(jié)果:
query()開始執(zhí)行!
OrderService.query()
query()執(zhí)行完畢灼芭!
==================
update()開始執(zhí)行有额!
OrderService.update()
update()執(zhí)行完畢!
假如我們現(xiàn)在新建了一個用戶表,需要開發(fā)用戶的查詢和更新方法的代碼巍佑,同時要求在方法執(zhí)行前后輸出日志茴迁,那么我們就可以非常方便的完成。
只需要寫一個用戶服務(wù)類 UserService 實(shí)現(xiàn) DaoService 接口句狼,在調(diào)用 UserService 方法的時候把 UserService 類的對象傳遞給代理類 DaoServiceProxy 類笋熬,然后調(diào)用代理類的 query() 方法和 update() 方法,就可以完美的打印出方法調(diào)用前后的日志腻菇。
// 用戶服務(wù)類UserService
class UserService implements DaoService {
@Override
public void query() {
System.out.println("UserService.query()");
}
@Override
public void update() {
System.out.println("UserService.update()");
}
}
public class TestStaticProxy2 {
public static void main(String[] args) {
// 創(chuàng)建被代理類的對象 userService
UserService userService = new UserService();
// 創(chuàng)建代理類的對象
DaoServiceProxy orderServiceProxy = new DaoServiceProxy(userService);
orderServiceProxy.query();
System.out.println("==================");
orderServiceProxy.update();
}
}
運(yùn)行結(jié)果:
query()開始執(zhí)行!
UserService.query()
query()執(zhí)行完畢昔馋!
==================
update()開始執(zhí)行筹吐!
UserService.update()
update()執(zhí)行完畢!
從以上的代碼我們可以看出秘遏,使用了代理設(shè)計模式會使我們實(shí)際開發(fā)過程中的代碼量大幅減少丘薛,這就是使用設(shè)計模式的威力!
二邦危、動態(tài)代理
動態(tài)代理介紹與代碼實(shí)現(xiàn)
靜態(tài)代理的特點(diǎn)是一個代理類只能為一個接口服務(wù)洋侨,這樣的話程序開發(fā)中必然會產(chǎn)生過多的代理類。
比如程序中除了有數(shù)據(jù)庫操作 DaoService 接口倦蚪,還有支付操作 PayService 接口希坚,PayService 接口里定義了支付方法 pay(),要求在 pay() 方法的執(zhí)行前后也要加兩行日志的輸出陵且。
這樣的話裁僧,不得不再建立一個實(shí)現(xiàn)了 PayService 接口的代理類,用來代理實(shí)現(xiàn)了 PayService 接口的被代理類的操作慕购。如果還有其他的接口聊疲,則需要繼續(xù)創(chuàng)建代理類,因此最好的解決辦法是通過一個動態(tài)代理類完成所有接口的方法執(zhí)行前后的日志輸出功能沪悲。
在 Java 中實(shí)現(xiàn)動態(tài)代理機(jī)制获洲,需要使用 java.lang.reflect.InvocationHandler 接口以及 java.lang.reflect.Proxy 類。
InvocationHandler 接口的定義如下:
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
在 InvocationHandler 接口中只定義了一個 invoke() 方法殿如,此方法中有 3 個參數(shù):
1讼渊、Object proxy:被代理的對象
2、Method method:要調(diào)用的方法
3啸澡、Object[] args:方法調(diào)用時需要傳入的參數(shù)
Proxy 類是用來創(chuàng)建代理類的丹弱,它通過 newProxyInstance() 方法為一個或多個接口動態(tài)的生成實(shí)現(xiàn)類。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
1谨胞、ClassLoader loader:類加載器
2固歪、Class<?>[] interfaces:獲取被代理類的全部接口
3、InvocationHandler h:InvocationHandler 接口的子類的實(shí)例
下面我們用動態(tài)代理來實(shí)現(xiàn)剛才提到的需求場景的日志打印
//PayService接口
interface PayService {
void pay();
}
//被代理類WeChatPayService
class WeChatPayService implements PayService {
@Override
public void pay() {
System.out.println("WeChatPayService.pay()");
}
}
// 動態(tài)代理類DynamicProxy
class DynamicProxy implements InvocationHandler {
Object obj; // 實(shí)現(xiàn)了接口的被代理類的對象的聲明
// 1、給被代理類的對象實(shí)例化牢裳,并且返回一個代理類對象逢防,體會一下反射是動態(tài)語言的關(guān)鍵
public Object getInstance(Object obj) {
this.obj = obj;
Class<?> clazz = obj.getClass();
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
// 當(dāng)通過代理類的對象發(fā)起對被重寫的方法的調(diào)用時,都會轉(zhuǎn)換為對如下的invoke方法的調(diào)用
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(method.getName() + "開始執(zhí)行蒲讯!");
// returnVal就是調(diào)用被重寫的方法的返回值
Object returnVal = method.invoke(obj, args);
System.out.println(method.getName() + "執(zhí)行完畢忘朝!");
return returnVal;
}
}
// 測試
public class TestDynamicProxy {
public static void main(String[] args) {
System.out.println("1、動態(tài)代理orderService");
// 被代理類的對象orderService
OrderService orderService = new OrderService();
// 創(chuàng)建一個實(shí)現(xiàn)了InvocationHandler接口的類的對象
DynamicProxy proxy = new DynamicProxy();
// 調(diào)用getInstance()方法判帮,動態(tài)的返回一個同樣實(shí)現(xiàn)了被代理類OrderService類實(shí)現(xiàn)的接口DaoService的代理類的對象
// dao就是代理類對象
DaoService order = (DaoService) proxy.getInstance(orderService);
order.query(); // 調(diào)用代理類對象的方法就會轉(zhuǎn)換為調(diào)用handler類里invoke方法的調(diào)用
System.out.println("==================");
order.update();
System.out.println("2局嘁、動態(tài)代理UserService");
UserService userService = new UserService();
DaoService user = (DaoService) proxy.getInstance(userService);
user.query();
System.out.println("==================");
user.update();
System.out.println("3、動態(tài)代理WeChatPayService");
WeChatPayService weChatPayService = new WeChatPayService();
PayService weChat = (PayService) proxy.getInstance(weChatPayService);
weChat.pay();
}
}
運(yùn)行結(jié)果:
1晦墙、動態(tài)代理orderService
query開始執(zhí)行悦昵!
OrderService.query()
query執(zhí)行完畢!
==================
update開始執(zhí)行晌畅!
OrderService.update()
update執(zhí)行完畢但指!
2、動態(tài)代理UserService
query開始執(zhí)行抗楔!
UserService.query()
query執(zhí)行完畢棋凳!
==================
update開始執(zhí)行!
UserService.update()
update執(zhí)行完畢连躏!
3剩岳、動態(tài)代理WeChatPayService
pay開始執(zhí)行!
WeChatPayService.pay()
pay執(zhí)行完畢反粥!
從以上的代碼以及運(yùn)行結(jié)果可以看出卢肃,動態(tài)代理可以對任意的接口實(shí)現(xiàn)類進(jìn)行代理,避免了靜態(tài)代理需要針對不同的接口開發(fā)對應(yīng)的代理類才顿。
動態(tài)代理可以對任何接口的所有方法實(shí)現(xiàn)前后增加相同功能的目的莫湘,Spring 框架中的 AOP 就是采用了動態(tài)代理的設(shè)計模式。AOP 切面編程對系統(tǒng)的設(shè)計與編碼具有非常重要的作用郑气,對于日志幅垮、事務(wù)、權(quán)限校驗(yàn)等可以在系統(tǒng)設(shè)計的階段不予考慮尾组,在設(shè)計后通過 AOP 的方式實(shí)現(xiàn)忙芒。
三、動態(tài)代理的實(shí)現(xiàn)模式
動態(tài)代理的實(shí)現(xiàn)模式有兩種:
1讳侨、用 JDK 實(shí)現(xiàn):代理對象必須實(shí)現(xiàn)一個接口呵萨,否則無法使用 JDK 自帶的動態(tài)代理。上面的例子就是用 JDK 實(shí)現(xiàn)的動態(tài)代理跨跨。
2潮峦、用 CGLIB 實(shí)現(xiàn):代理對象可以不實(shí)現(xiàn)接口囱皿,但是代理方法不能用 final 修飾。
用 CGLIB 實(shí)現(xiàn)動態(tài)代理
JDK 動態(tài)代理中提供一個 Proxy 類來創(chuàng)建代理類忱嘹,而在 CGLib 動態(tài)代理中也提供了一個類 Enhancer 來創(chuàng)建代理類嘱腥。使用 CGLib 創(chuàng)建動態(tài)代理類需要在項目中引入 CGLib 的 jar 包。
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.5</version>
</dependency>
使用CGLib 實(shí)現(xiàn)動態(tài)代理拘悦,首先需要動態(tài)代理類實(shí)現(xiàn) MethodInterceptor 方法攔截器接口齿兔,然后通過構(gòu)造函數(shù)傳遞被代理對象,然后利用 Enhancer 來實(shí)例化被代理對象础米,通過 Enhancer 設(shè)置被代理對象的字節(jié)碼文件分苇、設(shè)置回調(diào)函數(shù)、創(chuàng)建被代理對象椭盏,最后覆寫 intercept() 方法组砚。
使用CGLib 實(shí)現(xiàn)動態(tài)代理的示例代碼如下:
// CGLIB動態(tài)代理類
class CglibProxy implements MethodInterceptor {
private Object obj; // 被代理對象
public CglibProxy(Object obj) {
this.obj = obj;
}
// 給目標(biāo)對象創(chuàng)建一個被代理對象的示例
public Object getInstance() {
// 創(chuàng)建Enhancer對象,類似于JDK動態(tài)代理的Proxy類
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(obj.getClass()); // 設(shè)置被代理對象的字節(jié)碼文件
enhancer.setCallback(this);// 設(shè)置回調(diào)函數(shù)
return enhancer.create();// 創(chuàng)建被代理對象(子類)
}
@Override
public Object intercept(Object obj, Method method, Object[] objects, MethodProxy proyx) throws Throwable {
System.out.println("cglib動態(tài)代理開始");
Object object = proyx.invokeSuper(obj, objects); // 關(guān)鍵代碼
System.out.println("cglib動態(tài)代理結(jié)束");
return object;
}
}
// 測試
public class TestCglib {
public static void main(String[] args) {
System.out.println("1掏颊、動態(tài)代理orderService");
DaoService orderService = new OrderService();
CglibProxy orderProxy = new CglibProxy(orderService);
OrderService order = (OrderService) orderProxy.getInstance();
order.query();
System.out.println("==================");
order.update();
System.out.println("2、動態(tài)代理UserService");
DaoService userService = new UserService();
CglibProxy userProxy = new CglibProxy(userService);
UserService user = (UserService) userProxy.getInstance();
user.query();
System.out.println("==================");
user.update();
System.out.println("3艾帐、動態(tài)代理WeChatPayService");
PayService payService = new WeChatPayService();
CglibProxy payProxy = new CglibProxy(payService);
WeChatPayService weChat = (WeChatPayService) payProxy.getInstance();
weChat.pay();
}
}
本文詳細(xì)介紹了靜態(tài)代理和動態(tài)代理的作用和實(shí)現(xiàn)方式乌叶,并介紹了動態(tài)代理實(shí)現(xiàn)的兩種方式,在一般的開發(fā)中很少會使用到動態(tài)代理柒爸,但是在編寫一些底層代碼或者框架代碼的時候動態(tài)代理模式就比較常用了准浴,掌握動態(tài)代理的實(shí)現(xiàn)模式是一個程序員走向高級的必備技能。
下一篇文章將會為大家介紹如何使用動態(tài)代理模擬 Spring 框架的 AOP 切面編程捎稚,實(shí)現(xiàn)統(tǒng)計方法執(zhí)行前后共花費(fèi)的時間功能乐横,敬請關(guān)注.....