先了解下代理模式的概念:為其他對象提供一種代理傻咖,以控制對這個對象的訪問它碎。也就是通過一個新的對象去代表目標(biāo)對象,再通過新對象間接去訪問目標(biāo)對象的功能棚菊,主要的作用有:
- 增強(qiáng)目標(biāo)對象的功能
- 保護(hù)目標(biāo)對象浸踩,屏蔽掉一些敏感的功能
具體的實(shí)現(xiàn)可以采用靜態(tài)代理或者動態(tài)代理兩種方案,我們增強(qiáng)目標(biāo)對象的功能為例來學(xué)習(xí)動態(tài)代理统求。
注意检碗,有些說法是“代理模式一般是內(nèi)部創(chuàng)建被代理的對象” ,你這例子是裝飾者模式码邻,但我認(rèn)為這并不是必須折剃,更多的時候需要根據(jù)業(yè)務(wù)去權(quán)衡,比如在某些場景上要求代理類有一定通用性像屋,目標(biāo)對象是可以依賴注入的怕犁,就需要變通了。
一己莺、靜態(tài)代理
首先定義一個Shop
接口奏甫,作用就是售賣物品:
public interface Shop {
void sale(String name);
}
再定義一個CoffeeShop
類,實(shí)現(xiàn)Shop
接口凌受,專門售賣咖啡:
public class CoffeeShop implements Shop {
public void sale(String name) {
System.out.println("開始制作" + name + "......制作完成阵子!");
}
}
最后定義代理類,需要通過構(gòu)造函數(shù)注入目標(biāo)對象胜蛉,調(diào)用目標(biāo)對象的售賣方法款筑,并在售賣的前后添加問候語:
public class StaticProxy implements Shop {
private Shop shop;
public StaticProxy(Shop shop) {
this.shop = shop;
}
public void sale(String name) {
System.out.println("歡迎智蝠!");
shop.sale(name);
System.out.println("再見腾么!");
}
}
public class ProxyTest {
@Test
public void staticProxyTest() {
Shop shop = new CoffeeShop();
StaticProxy staticProxy = new StaticProxy(shop);
staticProxy.sale("拿鐵");
}
}
測試結(jié)果如下:
動態(tài)代理我們主要學(xué)習(xí)兩種:JDK動態(tài)代理奈梳、CGLIB動態(tài)代理,在上邊的例子基礎(chǔ)上進(jìn)行改進(jìn)解虱。
二攘须、JDK動態(tài)代理
JDK動態(tài)代理主要依賴java.lang.reflect
包下的相關(guān)類實(shí)現(xiàn),但要求被代理類必須最少實(shí)現(xiàn)一個接口于宙,來規(guī)定類要實(shí)現(xiàn)哪些方法捞魁,否則無法創(chuàng)建代理對象,所以可以繼續(xù)使用上邊的CoffeeShop
類昆著。
public class JdkProxy {
public Shop createProxy(final Shop shop) {
ClassLoader loader = shop.getClass().getClassLoader();
Class[] interfaces = shop.getClass().getInterfaces();
InvocationHandler h = new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("歡迎梧宫!");
Object result = method.invoke(shop, args);
System.out.println("再見塘匣!");
return result;
}
};
return (Shop) Proxy.newProxyInstance(loader, interfaces, h);
}
}
可以看到跑揉,上邊的Proxy.newProxyInstance()
方法就是用來創(chuàng)建代理對象的,需要三個參數(shù):
- ClassLoader :一般我們使用目標(biāo)對象的類加載器。
- Class[]:被代理的類實(shí)現(xiàn)接口的字節(jié)碼數(shù)組脱衙,保證代理對象和目標(biāo)對象有相同的方法退唠,要不然還怎么代理。這也是為什么要求被代理的類必須最少實(shí)現(xiàn)一個接口了。
- InvocationHandler :一般可以用匿名內(nèi)部類,在其
invoke()
回調(diào)中完成目標(biāo)對象的功能增強(qiáng)硝枉。
invoke()
方法三個參數(shù)的作用:
- Object proxy:創(chuàng)建的代理對象璃诀。
- Method method:當(dāng)前調(diào)用的代理對象的方法棕诵。
- Object[] args:當(dāng)前調(diào)用的代理對象方法傳遞的參數(shù)牧抵。
這樣就可以通過反射間接調(diào)用目標(biāo)對象的對應(yīng)方法了:Object result = method.invoke(shop, args)
接下來就是測試代碼了:
public class ProxyTest {
@Test
public void jdkProxyTest() {
Shop shop = new CoffeeShop();
JdkProxy jdkProxy = new JdkProxy();
Shop shopProxy = jdkProxy.createProxy(shop);
shopProxy.sale("拿鐵");
}
}
三、CGLIB動態(tài)代理
之前提到過蠢正,JDK動態(tài)代理要求被代理類必須最少實(shí)現(xiàn)一個接口,否則無法創(chuàng)建代理對象。但我們并不能要求用到的類都實(shí)現(xiàn)了接口持寄,所以就需要第三方的 CGLIB 庫來實(shí)現(xiàn)動態(tài)代理了,jar自行導(dǎo)入即可。下面用 CGLIB 來改造代碼:
public class CglibProxy {
public Object createProxy(final Object shop) {
Class clazz = shop.getClass();
Callback callback = new MethodInterceptor() {
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
System.out.println("歡迎沧卢!");
Object result = method.invoke(shop, args);
System.out.println("再見!");
return result;
}
};
return Enhancer.create(clazz, callback);
}
}
其實(shí)和 JDK動態(tài)代理的使用步驟類似但狭。
這里用Enhancer.create()
方法來創(chuàng)建代理對象披诗,需要兩個參數(shù):
- Class :被代理對象的字節(jié)碼。
- Callback :這里用到了它的
MethodInterceptor
子類立磁,并在intercept()
回調(diào)中完成目標(biāo)對象的功能增強(qiáng)呈队,這點(diǎn)和JDK動態(tài)代理類似。
通過如下測試代碼可以實(shí)現(xiàn)相同的效果:
public class ProxyTest {
@Test
public void cglibProxyTest() {
CoffeeShop2 shop = new CoffeeShop2();
CglibProxy cglibProxy = new CglibProxy();
CoffeeShop2 shopProxy = (CoffeeShop2) cglibProxy.createProxy(shop);
shopProxy.sale("拿鐵");
}
}
其實(shí) CGLIB 庫也可以用來實(shí)現(xiàn)JDK動態(tài)代理例子中的功能唱歧,因?yàn)?code>Enhancer.create()還有其他的重載方法: