
設(shè)計(jì)模式是語言的表達(dá)方式,它能讓語言輕便而富有內(nèi)涵、易讀卻功能強(qiáng)大。代理模式在Java中十分常見,有為擴(kuò)展某些類的功能而使用靜態(tài)代理,也有如Spring實(shí)現(xiàn)AOP而使用動(dòng)態(tài)代理,更有RPC實(shí)現(xiàn)中使用的調(diào)用端調(diào)用的代理服務(wù)。代理模型除了是一種設(shè)計(jì)模式之外,它更是一種思維,所以探討并深入理解這種模型是非常有必要的。
本文轉(zhuǎn)載自
-
代理(Proxy)是一種設(shè)計(jì)模式,提供了對(duì)目標(biāo)對(duì)象另外的訪問方式;即通過代理對(duì)象訪問目標(biāo)對(duì)象.這樣做的好處是:可以在目標(biāo)對(duì)象實(shí)現(xiàn)的基礎(chǔ)上,增強(qiáng)額外的功能操作,即擴(kuò)展目標(biāo)對(duì)象的功能.這里使用到編程中的一個(gè)思想:不要隨意去修改別人已經(jīng)寫好的代碼或者方法,如果需改修改,可以通過代理的方式來擴(kuò)展該方法。
代理模式的關(guān)鍵點(diǎn)是:代理對(duì)象與目標(biāo)對(duì)象.代理對(duì)象是對(duì)目標(biāo)對(duì)象的擴(kuò)展,并會(huì)調(diào)用目標(biāo)對(duì)象
代理的實(shí)現(xiàn)可以分為靜態(tài)代理和動(dòng)態(tài)代理,動(dòng)態(tài)代理又分為JDK動(dòng)態(tài)代理和CGlib動(dòng)態(tài)代理,下面我們依次來說明一下這三種方式:
一、靜態(tài)代理
靜態(tài)代理在使用時(shí),需要定義接口或者父類,被代理對(duì)象與代理對(duì)象一起實(shí)現(xiàn)相同的接口或者是繼承相同父類.
下面舉個(gè)案例來解釋:
模擬保存動(dòng)作,定義一個(gè)保存動(dòng)作的接口:IUserDao.java,然后目標(biāo)對(duì)象實(shí)現(xiàn)這個(gè)接口的方法UserDao.java,此時(shí)如果使用靜態(tài)代理方式,就需要在代理對(duì)象(UserDaoProxy.java)中也實(shí)現(xiàn)IUserDao接口.調(diào)用的時(shí)候通過調(diào)用代理對(duì)象的方法來調(diào)用目標(biāo)對(duì)象.
需要注意的是,代理對(duì)象與目標(biāo)對(duì)象要實(shí)現(xiàn)相同的接口,然后通過調(diào)用相同的方法來調(diào)用目標(biāo)對(duì)象的方法.
代碼示例:
接口:IUserDao.java
/**
* 接口
*/
public interface IUserDao {
void save();
}
目標(biāo)對(duì)象:UserDao.java
/**
* 接口實(shí)現(xiàn)
* 目標(biāo)對(duì)象
*/
public class UserDao implements IUserDao {
public void save() {
System.out.println("----已經(jīng)保存數(shù)據(jù)!----");
}
}
代理對(duì)象:UserDaoProxy.java
/**
* 代理對(duì)象,靜態(tài)代理
*/
public class UserDaoProxy implements IUserDao{
//接收保存目標(biāo)對(duì)象
private IUserDao target;
public UserDaoProxy(IUserDao target){
this.target=target;
}
public void save() {
System.out.println("開始事務(wù)...");
target.save();//執(zhí)行目標(biāo)對(duì)象的方法
System.out.println("提交事務(wù)...");
}
}
測(cè)試類:App.java
/**
* 測(cè)試類
*/
public class App {
public static void main(String[] args) {
//目標(biāo)對(duì)象
UserDao target = new UserDao();
//代理對(duì)象,把目標(biāo)對(duì)象傳給代理對(duì)象,建立代理關(guān)系
UserDaoProxy proxy = new UserDaoProxy(target);
proxy.save();//執(zhí)行的是代理的方法
}
}
靜態(tài)代理總結(jié):
1.可以做到在不修改目標(biāo)對(duì)象的功能前提下,對(duì)目標(biāo)功能擴(kuò)展.
2.缺點(diǎn):
因?yàn)榇韺?duì)象需要與目標(biāo)對(duì)象實(shí)現(xiàn)一樣的接口,所以會(huì)有很多代理類,類太多.同時(shí),一旦接口增加方法,目標(biāo)對(duì)象與代理對(duì)象都要維護(hù).
如何解決靜態(tài)代理中的缺點(diǎn)呢?答案是可以使用動(dòng)態(tài)代理方式.
二、JDK動(dòng)態(tài)代理
動(dòng)態(tài)代理有以下特點(diǎn):
1.代理對(duì)象,不需要實(shí)現(xiàn)接口
2.代理對(duì)象的生成,是利用JDK的API,動(dòng)態(tài)的在內(nèi)存中構(gòu)建代理對(duì)象(需要我們指定創(chuàng)建代理對(duì)象/目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型)
3.動(dòng)態(tài)代理也叫做:JDK代理,接口代理
JDK中生成代理對(duì)象的API
代理類所在包:java.lang.reflect.Proxy
JDK實(shí)現(xiàn)代理只需要使用newProxyInstance方法,但是該方法需要接收三個(gè)參數(shù),完整的寫法是:
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
注意該方法是在Proxy類中是靜態(tài)方法,且接收的三個(gè)參數(shù)依次為:
- ClassLoader loader,:指定當(dāng)前目標(biāo)對(duì)象使用類加載器,獲取加載器的方法是固定的
- Class<?>[] interfaces,:目標(biāo)對(duì)象實(shí)現(xiàn)的接口的類型,使用泛型方式確認(rèn)類型
- InvocationHandler h:事件處理,執(zhí)行目標(biāo)對(duì)象的方法時(shí),會(huì)觸發(fā)事件處理器的方法,會(huì)把當(dāng)前執(zhí)行目標(biāo)對(duì)象的方法作為參數(shù)傳入
代碼示例:
接口類IUserDao.java以及接口實(shí)現(xiàn)類,目標(biāo)對(duì)象UserDao是一樣的,沒有做修改.在這個(gè)基礎(chǔ)上,增加一個(gè)代理工廠類(ProxyFactory.java),將代理類寫在這個(gè)地方,然后在測(cè)試類(需要使用到代理的代碼)中先建立目標(biāo)對(duì)象和代理對(duì)象的聯(lián)系,然后代用代理對(duì)象的中同名方法
代理工廠類:ProxyFactory.java
/**
* 創(chuàng)建動(dòng)態(tài)代理對(duì)象
* 動(dòng)態(tài)代理不需要實(shí)現(xiàn)接口,但是需要指定接口類型
*/
public class ProxyFactory{
//維護(hù)一個(gè)目標(biāo)對(duì)象
private Object target;
public ProxyFactory(Object target){
this.target=target;
}
//給目標(biāo)對(duì)象生成代理對(duì)象
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í)行目標(biāo)對(duì)象方法
Object returnValue = method.invoke(target, args);
System.out.println("提交事務(wù)2");
return returnValue;
}
}
);
}
}
測(cè)試類:App.java
/**
* 測(cè)試類
*/
public class App {
public static void main(String[] args) {
// 目標(biāo)對(duì)象
IUserDao target = new UserDao();
// 【原始的類型 class cn.itcast.b_dynamic.UserDao】
System.out.println(target.getClass());
// 給目標(biāo)對(duì)象,創(chuàng)建代理對(duì)象
IUserDao proxy = (IUserDao) new ProxyFactory(target).getProxyInstance();
// class $Proxy0 內(nèi)存中動(dòng)態(tài)生成的代理對(duì)象
System.out.println(proxy.getClass());
// 執(zhí)行方法 【代理對(duì)象】
proxy.save();
}
}
總結(jié):
代理對(duì)象不需要實(shí)現(xiàn)接口,但是目標(biāo)對(duì)象一定要實(shí)現(xiàn)接口,否則不能用動(dòng)態(tài)代理
二、CGLIB代理
CGLIB概述
上面的靜態(tài)代理和動(dòng)態(tài)代理模式都是要求目標(biāo)對(duì)象是實(shí)現(xiàn)一個(gè)接口的目標(biāo)對(duì)象,但是有時(shí)候目標(biāo)對(duì)象只是一個(gè)單獨(dú)的對(duì)象,并沒有實(shí)現(xiàn)任何的接口,這個(gè)時(shí)候就可以使用以目標(biāo)對(duì)象子類的方式類實(shí)現(xiàn)代理,這種方法就叫做:Cglib代理
CGLIB也提供了一個(gè)處理器接口(這里成為回調(diào)接口)net.sf.cglib.proxy.MethodInterceptor(相當(dāng)于JDK代理的InvocationHandler接口),它自定義了一個(gè)intercept方法(相當(dāng)于JDK代理的invoke方法),用于集中處理在動(dòng)態(tài)代理類對(duì)象上的方法調(diào)用,通常在該方法中實(shí)現(xiàn)對(duì)委托類的代理訪問。
// 該方法負(fù)責(zé)集中處理動(dòng)態(tài)代理類上的所有方法調(diào)用。第一個(gè)參數(shù)是代理類對(duì)象,第二個(gè)參數(shù)是委托類被調(diào)用的方法的類對(duì)象
// 第三個(gè)是該方法的參數(shù),第四個(gè)是生成在代理類里面,除了方法名不同,其他都和被代理方法一樣的(參數(shù),方法體里面的東西)一個(gè)方法的類對(duì)象。
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodproxy) throws Throwable
然后CGLIB也提供了一個(gè)生成代理類的類net.sf.cglib.proxy.Enhancer(相當(dāng)于JDK代理的java.lang.reflect.Proxy),它提供了一組靜態(tài)方法來為一組接口動(dòng)態(tài)地生成代理類及其對(duì)象。本文創(chuàng)建代理用到的Enhancer的靜態(tài)方法如下:
public Object intercept(Object proxy,Method method, Object[] args, MethodProxy methodproxy) throws Throwable
//為指定類裝載器、接口及調(diào)用處理器生成動(dòng)態(tài)代理類實(shí)例
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){}
這個(gè)create方法相當(dāng)于JDK代理的newProxyInstance方法,該方法的參數(shù)具體含義如下:
- Class class:指定一個(gè)被代理類的類對(duì)象。
- Class[]interfaces:如果要代理接口,指定一組被代理類實(shí)現(xiàn)的所有接口的類對(duì)象。
- Callback h:指定一個(gè)實(shí)現(xiàn)了處理器接口(這里稱回調(diào)接口)的對(duì)象。
- CallbackFilter filter:指定一個(gè)方法過濾器。
- Callback[]hs:指定一組實(shí)現(xiàn)了處理器接口的對(duì)象。
- Class[] argumentTypes:指定某個(gè)構(gòu)造器的參數(shù)類型
- Object[] arguments:指定某個(gè)gouz
如何使用?
- 通過實(shí)現(xiàn) MethodInterceptor接口創(chuàng)建自己的處理器;
- 通過給Enhancer類的create()方法傳進(jìn)被代理類的類對(duì)象、實(shí)現(xiàn)了MethodInterceptor接口的處理器對(duì)象,得到一個(gè)代理類對(duì) 象。
- 其中Enhancer類通過反射機(jī)制獲得代理類的構(gòu)造函數(shù),有一個(gè)參數(shù)類型是處理器接口類型。
- Enhancer類再通過構(gòu)造函數(shù)對(duì)象創(chuàng)建動(dòng)態(tài)代理類實(shí)例,構(gòu)造時(shí)處理器對(duì)象作為參數(shù)被傳入。
- 當(dāng)客戶端調(diào)用了代理類的方法時(shí),該方法調(diào)用了處理器的intercept()方法并給intercept()方法傳進(jìn)委托類方法的類對(duì)象,intercept方法再調(diào)用委托類的真實(shí)方法。
(1)建一個(gè)reallyCglibProxy包,所有程序都放在該包下,我在Spring的包庫里面找到兩個(gè)包:asm.jar和cblib-2.1.3.jar,最好在Spring里面同時(shí)拷貝這兩個(gè)包到項(xiàng)目的類庫下,不要分別從網(wǎng)上下載,可能會(huì)沖突。
(2)建一個(gè)被代理類(RealSubject.java)。
package reallyCglibProxy;
//被代理類
public class RealSubject {
public void print() {
System.out.println("被代理的人郭襄");
}
}
(3)建一個(gè)用戶自定義的處理器類LogHandler.java,需要實(shí)現(xiàn)處理接口,在intercept()方法里寫上被代理類的方法調(diào)用前后要進(jìn)行的動(dòng)作。這個(gè)intercept()方法我們不用直接調(diào)用,是讓將來自動(dòng)生成的代理類去調(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;
//相當(dāng)于實(shí)現(xiàn)jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
private Object delegate; //被代理類的對(duì)象
//綁定被代理類的對(duì)象
public Object bind(Object delegate)throws Exception{
this.delegate=delegate;
return Enhancer.create(delegate.getClass(),this);
}
//相當(dāng)于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生成的代理類,可參考《五》中這個(gè)工具類
LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
return result;
}
}
(4)編寫測(cè)試客戶端(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é)果可以看出,成功自動(dòng)生成了代理類RealSubject48574fb2,并成功實(shí)現(xiàn)了代理的效果,而且還代理了RealSubject類從父類繼承的finalize、equals、toString、hashCode、clone這幾個(gè)方法。
CBLIB方法過濾器
如果現(xiàn)在有個(gè)要求,被代理類的print方法不用處理。當(dāng)最簡(jiǎn)單的方式是修改方法攔截器(即intercept方法),在里面進(jìn)行判斷,如果是print()方法就不做任何邏輯處理,直接調(diào)用,這樣子使得編寫攔截器(相當(dāng)于JDK代理里的處理器)邏輯變復(fù)雜,不易維護(hù)。我們可以使用CGLIB提供的方法過濾器(CallbackFilter),使得被代理類中不同的方法,根據(jù)我們的邏輯要求,調(diào)用不同的處理器,從而使用不同的攔截方法(處理器方法)。
基本步驟如下:
(1)修改一下被代理類(RealSubject.java),增加一個(gè)方法print2。
package reallyCglibProxy;
//被代理類
public class RealSubject {
public void print() {
System.out.println("被代理的人郭襄");
}
public void print2() {
System.out.println("我是print2方法哦");
}
}
(2)新建一個(gè)用戶自定義的方法過濾器類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),所有被代理的方法都會(huì)被過濾一次。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這幾個(gè)方法)都被調(diào)用accept方法進(jìn)行過濾,給每個(gè)方法返回一個(gè)整數(shù),
本例子是0或者1,從而選擇不同的處理器。
*/
System.out.println(method.getDeclaringClass()+"類的"+method.getName()+"方法被檢查過濾!");
/*
如果調(diào)用是print方法,則要調(diào)用0號(hào)位的攔截器去處理
*/
if("print".equals(method.getName()))
//0號(hào)位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的this,它在這個(gè)數(shù)組第0位置
return 1;
/*
1號(hào)位即LogHandler里面 new Callback[]{this,NoOp.INSTANCE}中的NoOp.INSTANCE,它在這個(gè)數(shù)組第1位置。
NoOp.INSTANCE是指不做任何事情的攔截器。
在這里就是任何人都有權(quán)限訪問print方法,即這個(gè)方法沒有代理,直接調(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;
//相當(dāng)于實(shí)現(xiàn)jdk的InvocationHandler接口
public class LogHandler implements MethodInterceptor{
private Object delegate; //被代理類的對(duì)象
//綁定被代理類的對(duì)象
public Object bind(Object delegate)throws Exception{
this.delegate=delegate;
/**
*傳進(jìn)不同的攔截器(相當(dāng)于JDK代理里面的處理器),NoOp.INSTANCE是cglib已經(jīng)寫好的不做任何事情的攔截器,傳進(jìn)去的new Callback[]是一個(gè)數(shù)組,現(xiàn)在數(shù)組有兩個(gè)攔截器對(duì)象,分別是this,和NoOp.INSTANCE,它們分別在數(shù)組的第0位和第一位對(duì)應(yīng)著自定義過濾器MyProxyFilter里的accept方法返回的整數(shù),如果是0就調(diào)用this攔截器,如果是1就調(diào)用NoOp.INSTANCE所以自定義過濾器MyProxyFilter里的accept方法返回的整數(shù)最大就是攔截器數(shù)組的長(zhǎng)度,比如本例子當(dāng)中,只能是0或者1,不能是2,因?yàn)檫@個(gè)數(shù)組只有兩個(gè)元素,最大位置就是1號(hào)位。
*/
return Enhancer.create(delegate.getClass(), null, new MyProxyFilter(), new Callback[]{this,NoOp.INSTANCE});
}
//相當(dāng)于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生成的代理類,可參考《五》中這個(gè)工具類
//LonHanderReflectTool.ReflectClass(proxy.getClass().getName());
return result;
}
}
(4) 修改測(cè)試客戶端(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這幾個(gè)方法)都被調(diào)用accept方法進(jìn)行過濾,給每個(gè)方法返回一個(gè)整數(shù),本例子是0或者1,從而選擇不同的處理器。
參考文章
本文作者: catalinaLi
本文鏈接: http://catalinali.top/2018/proxyPattern/