設(shè)計模式是語言的表達方式挎狸,它能讓語言輕便而富有內(nèi)涵部翘、易讀卻功能強大搂根。代理模式在Java中十分常見珍促,有為擴展某些類的功能而使用靜態(tài)代理,也有如Spring實現(xiàn)AOP而使用動態(tài)代理兄墅,更有RPC實現(xiàn)中使用的調(diào)用端調(diào)用的代理服務(wù)踢星。代理模型除了是一種設(shè)計模式之外,它更是一種思維隙咸,所以探討并深入理解這種模型是非常有必要的沐悦。
本文轉(zhuǎn)載自
-
代理(Proxy)是一種設(shè)計模式,提供了對目標對象另外的訪問方式;即通過代理對象訪問目標對象.這樣做的好處是:可以在目標對象實現(xiàn)的基礎(chǔ)上,增強額外的功能操作,即擴展目標對象的功能.這里使用到編程中的一個思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴展該方法。
代理模式的關(guān)鍵點是:代理對象與目標對象.代理對象是對目標對象的擴展,并會調(diào)用目標對象
代理的實現(xiàn)可以分為靜態(tài)代理和動態(tài)代理五督,動態(tài)代理又分為JDK動態(tài)代理和CGlib動態(tài)代理藏否,下面我們依次來說明一下這三種方式:
一、靜態(tài)代理
靜態(tài)代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現(xiàn)相同的接口或者是繼承相同父類.
下面舉個案例來解釋:
模擬保存動作,定義一個保存動作的接口:IUserDao.java,然后目標對象實現(xiàn)這個接口的方法UserDao.java,此時如果使用靜態(tài)代理方式,就需要在代理對象(UserDaoProxy.java)中也實現(xiàn)IUserDao接口.調(diào)用的時候通過調(diào)用代理對象的方法來調(diào)用目標對象.
需要注意的是,代理對象與目標對象要實現(xiàn)相同的接口,然后通過調(diào)用相同的方法來調(diào)用目標對象的方法.
代碼示例:
接口:IUserDao.java
/**
* 接口
*/
public interface IUserDao {
void save();
}
目標對象:UserDao.java
/**
* 接口實現(xiàn)
* 目標對象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
}
}
代理對象:UserDaoProxy.java
/**
* 代理對象,靜態(tài)代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目標對象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("開始事務(wù)...");
target.save();//執(zhí)行目標對象的方法
System.out.println("提交事務(wù)...");
}
}
測試類:App.java
/**
* 測試類
*/
public class App {
public static void main(String[] args) {
//目標對象
UserDao target = new UserDao();
//代理對象,把目標對象傳給代理對象,建立代理關(guān)系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//執(zhí)行的是代理的方法
}
}
靜態(tài)代理總結(jié):
1.可以做到在不修改目標對象的功能前提下,對目標功能擴展.
2.缺點:
因為代理對象需要與目標對象實現(xiàn)一樣的接口,所以會有很多代理類,類太多.同時,一旦接口增加方法,目標對象與代理對象都要維護.
如何解決靜態(tài)代理中的缺點呢?答案是可以使用動態(tài)代理方式.
二充包、JDK動態(tài)代理
動態(tài)代理有以下特點:
1.代理對象,不需要實現(xiàn)接口
2.代理對象的生成,是利用JDK的API,動態(tài)的在內(nèi)存中構(gòu)建代理對象(需要我們指定創(chuàng)建代理對象/目標對象實現(xiàn)的接口的類型)
3.動態(tài)代理也叫做:JDK代理,接口代理
JDK中生成代理對象的API
代理類所在包:java.lang.reflect.Proxy
JDK實現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個參數(shù),完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態(tài)方法,且接收的三個參數(shù)依次為:
- ClassLoader loader,:指定當前目標對象使用類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces,:目標對象實現(xiàn)的接口的類型,使用泛型方式確認類型
- InvocationHandler h:事件處理,執(zhí)行目標對象的方法時,會觸發(fā)事件處理器的方法,會把當前執(zhí)行目標對象的方法作為參數(shù)傳入
代碼示例:
接口類IUserDao.java以及接口實現(xiàn)類,目標對象UserDao是一樣的,沒有做修改.在這個基礎(chǔ)上,增加一個代理工廠類(ProxyFactory.java),將代理類寫在這個地方,然后在測試類(需要使用到代理的代碼)中先建立目標對象和代理對象的聯(lián)系,然后代用代理對象的中同名方法
代理工廠類:ProxyFactory.java
/**
* 創(chuàng)建動態(tài)代理對象
* 動態(tài)代理不需要實現(xiàn)接口,但是需要指定接口類型
*/
public class ProxyFactory{
//維護一個目標對象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//給目標對象生成代理對象
public Object getProxyInstance(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("開始事務(wù)2");
//執(zhí)行目標對象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務(wù)2");
return returnValue;
}
}
);
}
}
測試類:App.java
/**
* 測試類
*/
public class App {
public static void main(String[] args) {
// 目標對象
IUserDao target = new UserDao();
// 【原始的類型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 給目標對象副签,創(chuàng)建代理對象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 內(nèi)存中動態(tài)生成的代理對象
System.out.println(proxy.getClass());
// 執(zhí)行方法 【代理對象】
proxy.save();
}
}
總結(jié):
代理對象不需要實現(xiàn)接口,但是目標對象一定要實現(xiàn)接口,否則不能用動態(tài)代理
二、CGLIB代理
CGLIB概述
上面的靜態(tài)代理和動態(tài)代理模式都是要求目標對象是實現(xiàn)一個接口的目標對象,但是有時候目標對象只是一個單獨的對象,并沒有實現(xiàn)任何的接口,這個時候就可以使用以目標對象子類的方式類實現(xiàn)代理,這種方法就叫做:Cglib代理
CGLIB也提供了一個處理器接口(這里成為回調(diào)接口)net.sf.cglib.proxy.MethodInterceptor(相當于JDK代理的InvocationHandler接口)基矮,它自定義了一個intercept方法(相當于JDK代理的invoke方法)淆储,用于集中處理在動態(tài)代理類對象上的方法調(diào)用,通常在該方法中實現(xiàn)對委托類的代理訪問家浇。
// 該方法負責(zé)集中處理動態(tài)代理類上的所有方法調(diào)用本砰。第一個參數(shù)是代理類對象,第二個參數(shù)是委托類被調(diào)用的方法的類對象
// 第三個是該方法的參數(shù)钢悲,第四個是生成在代理類里面点额,除了方法名不同,其他都和被代理方法一樣的(參數(shù)莺琳,方法體里面的東西)一個方法的類對象还棱。
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable
然后CGLIB也提供了一個生成代理類的類net.sf.cglib.proxy.Enhancer(相當于JDK代理的java.lang.reflect.Proxy),它提供了一組靜態(tài)方法來為一組接口動態(tài)地生成代理類及其對象惭等。本文創(chuàng)建代理用到的Enhancer的靜態(tài)方法如下:
public Object intercept(Object proxy,Method method, Object[] args, MethodProxy methodproxy) throws Throwable
//為指定類裝載器珍手、接口及調(diào)用處理器生成動態(tài)代理類實例
public static Object create(Class class ,Callback callback h){}
public static Object create(Class class,Class[] interfaces,Callback h){}
public static Object create(Classclass, Class[] interfaces, CallbackFilter filter, Callback[] hs)
public Object create(Class[] argumentTypes, Object[] arguments){}
這個create方法相當于JDK代理的newProxyInstance方法,該方法的參數(shù)具體含義如下:
- Class class:指定一個被代理類的類對象。
- Class[]interfaces:如果要代理接口琳要,指定一組被代理類實現(xiàn)的所有接口的類對象料扰。
- Callback h:指定一個實現(xiàn)了處理器接口(這里稱回調(diào)接口)的對象。
- CallbackFilter filter:指定一個方法過濾器焙蹭。
- Callback[]hs:指定一組實現(xiàn)了處理器接口的對象。
- Class[] argumentTypes:指定某個構(gòu)造器的參數(shù)類型
- Object[] arguments:指定某個gouz
如何使用嫂伞?
- 通過實現(xiàn) MethodInterceptor接口創(chuàng)建自己的處理器孔厉;
- 通過給Enhancer類的create()方法傳進被代理類的類對象、實現(xiàn)了MethodInterceptor接口的處理器對象帖努,得到一個代理類對 象撰豺。
- 其中Enhancer類通過反射機制獲得代理類的構(gòu)造函數(shù),有一個參數(shù)類型是處理器接口類型拼余。
- Enhancer類再通過構(gòu)造函數(shù)對象創(chuàng)建動態(tài)代理類實例污桦,構(gòu)造時處理器對象作為參數(shù)被傳入。
- 當客戶端調(diào)用了代理類的方法時匙监,該方法調(diào)用了處理器的intercept()方法并給intercept()方法傳進委托類方法的類對象凡橱,intercept方法再調(diào)用委托類的真實方法。
(1)建一個reallyCglibProxy包亭姥,所有程序都放在該包下稼钩,我在Spring的包庫里面找到兩個包:asm.jar和cblib-2.1.3.jar,最好在Spring里面同時拷貝這兩個包到項目的類庫下达罗,不要分別從網(wǎng)上下載坝撑,可能會沖突。
(2)建一個被代理類(RealSubject.java)粮揉。
package reallyCglibProxy;
//被代理類
public class RealSubject {
public void print() {
System.out.println("被代理的人郭襄");
}
}
(3)建一個用戶自定義的處理器類LogHandler.java巡李,需要實現(xiàn)處理接口,在intercept()方法里寫上被代理類的方法調(diào)用前后要進行的動作扶认。這個intercept()方法我們不用直接調(diào)用侨拦,是讓將來自動生成的代理類去調(diào)用的。
package reallyCglibProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import jdkDynamicProxy.LonHanderReflectTool;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
//相當于實現(xiàn)jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
private Object delegate; //被代理類的對象
//綁定被代理類的對象
public Object bind(Object delegate)throws Exception{
this.delegate=delegate;
return Enhancer.create(delegate.getClass(),this);
}
//相當于InvocationHandler接口里面的invoke()方法
@Override
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodproxy) throws Throwable {
Object result=null;
System.out.println("我是代理人郭靖蝠引,開始代理");
//method.invoke()或者methodproxy.invoke()都可以
result=method.invoke(delegate,args);
//result=methodproxy.invoke(delegate,args);
System.out.println("我是代理人郭靖阳谍,代理完畢");
//調(diào)用工具類反射jdk的Proxy生成的代理類,可參考《五》中這個工具類
LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
return result;
}
}
(4)編寫測試客戶端(TestReallyCglibProxy.java)螃概。
package reallyCglibProxy;
public class TestReallyCglibProxy {
public static void main(String[] args)throws Exception {
RealSubject sub1=new RealSubject();
LogHandler hander=new LogHandler();
RealSubject sub2=(RealSubject)hander.bind(sub1);
sub2.print();
}
}
輸出結(jié)果:
我是代理人郭靖矫夯,開始代理
被代理的人郭襄
我是代理人郭靖,代理完畢
從結(jié)果可以看出吊洼,成功自動生成了代理類RealSubject48574fb2训貌,并成功實現(xiàn)了代理的效果,而且還代理了RealSubject類從父類繼承的finalize、equals递沪、toString豺鼻、hashCode、clone這幾個方法款慨。
CBLIB方法過濾器
如果現(xiàn)在有個要求儒飒,被代理類的print方法不用處理。當最簡單的方式是修改方法攔截器(即intercept方法)檩奠,在里面進行判斷桩了,如果是print()方法就不做任何邏輯處理,直接調(diào)用埠戳,這樣子使得編寫攔截器(相當于JDK代理里的處理器)邏輯變復(fù)雜井誉,不易維護。我們可以使用CGLIB提供的方法過濾器(CallbackFilter)整胃,使得被代理類中不同的方法颗圣,根據(jù)我們的邏輯要求,調(diào)用不同的處理器屁使,從而使用不同的攔截方法(處理器方法)在岂。
基本步驟如下:
(1)修改一下被代理類(RealSubject.java),增加一個方法print2屋灌。
package reallyCglibProxy;
//被代理類
public class RealSubject {
public void print() {
System.out.println("被代理的人郭襄");
}
public void print2() {
System.out.println("我是print2方法哦");
}
}
(2)新建一個用戶自定義的方法過濾器類MyProxyFilter.java洁段。
package reallyCglibProxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.CallbackFilter;
import net.sf.cglib.proxy.NoOp;
//自定義方法過濾器類
public class MyProxyFilterimplements CallbackFilter {
public int accept(Method method) {
/**
*這句話可發(fā)現(xiàn),所有被代理的方法都會被過濾一次共郭。Enhancer的源代碼中看到下面一段代碼
while(it1.hasNext()){
MethodInfomethod=(MethodInfo)it1.next();
MethodactualMethod=(it2!=null)?(Method)it2.next():null;
intindex=filter.accept(actualMethod);
if(index>=callbackTypes.length){
thrownewIllegalArgumentException("Callbackfilterreturnedanindexthatistoolarge:"+index);
}
上段代碼可以看到所有的被代理的方法祠丝,本例子中就是print、print2除嘹、從父類繼承的finalize写半、equals、toString尉咕、
hashCode叠蝇、clone這幾個方法)都被調(diào)用accept方法進行過濾,給每個方法返回一個整數(shù)年缎,
本例子是0或者1悔捶,從而選擇不同的處理器。
*/
System.out.println(method.getDeclaringClass()+"類的"+method.getName()+"方法被檢查過濾单芜!");
/*
如果調(diào)用是print方法蜕该,則要調(diào)用0號位的攔截器去處理
*/
if("print".equals(method.getName()))
//0號位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的this,它在這個數(shù)組第0位置
return 1;
/*
1號位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的NoOp.INSTANCE洲鸠,它在這個數(shù)組第1位置堂淡。
NoOp.INSTANCE是指不做任何事情的攔截器馋缅。
在這里就是任何人都有權(quán)限訪問print方法,即這個方法沒有代理绢淀,直接調(diào)用
*/
return 0;
}
}
(3)修改一下用戶自定義的處理器類LogHandler.java
package reallyCglibProxy;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import jdkDynamicProxy.LonHanderReflectTool;
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import net.sf.cglib.proxy.NoOp;
//相當于實現(xiàn)jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
private Object delegate; //被代理類的對象
//綁定被代理類的對象
public Object bind(Object delegate)throws Exception{
this.delegate=delegate;
/**
*傳進不同的攔截器(相當于JDK代理里面的處理器),NoOp.INSTANCE是cglib已經(jīng)寫好的不做任何事情的攔截器萤悴,傳進去的new Callback[]是一個數(shù)組,現(xiàn)在數(shù)組有兩個攔截器對象皆的,分別是this,和NoOp.INSTANCE覆履,它們分別在數(shù)組的第0位和第一位對應(yīng)著自定義過濾器MyProxyFilter里的accept方法返回的整數(shù),如果是0就調(diào)用this攔截器费薄,如果是1就調(diào)用NoOp.INSTANCE所以自定義過濾器MyProxyFilter里的accept方法返回的整數(shù)最大就是攔截器數(shù)組的長度内狗,比如本例子當中,只能是0或者1义锥,不能是2,因為這個數(shù)組只有兩個元素岩灭,最大位置就是1號位拌倍。
*/
return Enhancer.create(delegate.getClass(), null, new MyProxyFilter(), new Callback[]{this,NoOp.INSTANCE});
}
//相當于InvocationHandler接口里面的invoke()方法
public Object intercept(Object proxy, Method method, Object[] args,
MethodProxy methodproxy) throws Throwable {
Object result=null;
System.out.println("我是代理人郭靖,開始代理");
//method.invoke()或者methodproxy.invoke()都可以
result=method.invoke(delegate,args);
//result=methodproxy.invoke(delegate,args);
System.out.println("我是代理人郭靖噪径,代理完畢");
//調(diào)用工具類反射jdk的Proxy生成的代理類柱恤,可參考《五》中這個工具類
//LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
return result;
}
}
(4) 修改測試客戶端(TestReallyCglibProxy.java)。
package reallyCglibProxy;
publicclass TestReallyCglibProxy {
publicstaticvoid main(String[] args)throws Exception {
RealSubject sub1=new RealSubject();
LogHandler hander=new LogHandler();
RealSubject sub2=(RealSubject)hander.bind(sub1);
sub2.print();
sub2.print2();
}
}
輸出結(jié)果:
class reallyCglibProxy.RealSubject類的print2方法被檢查過濾找爱!
class reallyCglibProxy.RealSubject類的print方法被檢查過濾梗顺!
class java.lang.Object類的finalize方法被檢查過濾!
class java.lang.Object類的equals方法被檢查過濾车摄!
class java.lang.Object類的toString方法被檢查過濾寺谤!
class java.lang.Object類的hashCode方法被檢查過濾!
class java.lang.Object類的clone方法被檢查過濾吮播!
被代理的人郭襄
我是代理人郭靖变屁,開始代理
我是print2方法哦
我是代理人郭靖,代理完畢
從結(jié)果可以看出意狠,print方法沒有被代理粟关,print2方法被代理了,有了方法過濾器环戈,被代理類被代理的方法(本文例子中就是print闷板、print2、從父類繼承的finalize院塞、equals遮晚、toString、hashCode迫悠、clone這幾個方法)都被調(diào)用accept方法進行過濾鹏漆,給每個方法返回一個整數(shù),本例子是0或者1,從而選擇不同的處理器艺玲。
參考文章
本文作者: catalinaLi
本文鏈接: http://catalinali.top/2018/proxyPattern/