一、代理模式介紹
代理模式,非常容易理解,一眼看過(guò)去大家都懂,字面意思,如果不懂稍微琢磨琢磨就懂了,我在這里舉幾個(gè)栗子?? ,以便更容易理解。
- 賈乃亮作為一個(gè)明星,為了更專注于自己的工作,他找了一個(gè)經(jīng)紀(jì)人,幫他約綜藝、約電視劇、約電影等等。
- 英雄聯(lián)盟打的太菜了,但是為了沖到更高的段位,在別人面前裝裝13,于是找了一個(gè)代練幫忙上分。
- 背井離鄉(xiāng),來(lái)到一個(gè)陌生的城市打拼,不知道住在哪里怎么辦?我們可以通過(guò)中介更快的找到心儀的住所。
- 春運(yùn)的票實(shí)在太難搶,我們只能使用搶票軟件幫我們搶票。
可以看到,在生活中,代理無(wú)處不在,無(wú)論是經(jīng)紀(jì)人、游戲代練、房產(chǎn)中介、搶票軟件,都是幫我們做事,對(duì)我們的能力進(jìn)行了擴(kuò)展。
那么在Java中使用代理模式,通過(guò)代理對(duì)象訪問(wèn)目標(biāo)對(duì)象,從而在不改變目標(biāo)對(duì)象原有代碼的基礎(chǔ)上,進(jìn)行功能的擴(kuò)展和增強(qiáng)。
目前在Java中分為三種代理模式:靜態(tài)代理、jdk動(dòng)態(tài)代理、cglib動(dòng)態(tài)代理。
二、靜態(tài)代理
靜態(tài)代理,需要代理對(duì)象和目標(biāo)對(duì)象實(shí)現(xiàn)同一個(gè)接口。
現(xiàn)在市面上的游戲很多,我們創(chuàng)建一個(gè)接口,寫上現(xiàn)在一直很火的一款游戲,英雄聯(lián)盟
public interface Play {
public void lol();
}
有一個(gè)對(duì)象,他叫 “有趣的靈魂200斤”,是個(gè)肥宅,名如其人對(duì)吧,他有一個(gè)愛(ài)好,就是玩、娛樂(lè),我們讓肥宅小兄弟實(shí)現(xiàn)一下這個(gè)接口,看看他是怎么玩這個(gè)游戲的。
public class FeiZhai implements Play {
public void lol() {
System.out.println("B鍵已扣,不死不休!");
}
}
有一天我們的肥宅小兄弟,剛開(kāi)了新的一局英雄聯(lián)盟排位賽,突然肚子一陣作響,他看了看時(shí)間,于是拿起手機(jī)打開(kāi)餓不餓APP訂了個(gè)餐,然后繼續(xù)游戲。
那么我們的肥宅小兄弟為什么在網(wǎng)上訂餐呢,因?yàn)樗粫?huì)做飯,所以只能在網(wǎng)上訂餐,而且商家做好餐,由我們的外賣小哥送過(guò)來(lái),就可以吃了,很方便,主要是不耽誤玩游戲。
/**
* 餓不餓App-肥宅小兄弟
*/
public class EbueProxy implements Play {
//聲明目標(biāo)對(duì)象-為肥宅小兄弟服務(wù)
private FeiZhai feiZhai;
//通過(guò)構(gòu)造方法,獲取目標(biāo)對(duì)象,進(jìn)行賦值
public EbueProxy(Play play) {
this.feiZhai = (FeiZhai) play;
}
//對(duì)原來(lái)的方法進(jìn)行擴(kuò)展
public void lol() {
System.out.println("商家接到訂單,開(kāi)始做飯");
System.out.println("飯做完了,外賣小哥取到餐");
//調(diào)用目標(biāo)對(duì)象的方法
//在不影響肥宅小兄弟玩LOL的情況下,進(jìn)行功能的拓展和增強(qiáng)。
feiZhai.lol();
System.out.println("外賣小哥進(jìn)行配送");
System.out.println("叮咚!您好,您的餐,請(qǐng)慢用!");
}
}
我們的肥宅小兄弟,為了不耽誤自己玩游戲,委托餓不餓APP送餐上門,這就是一種代理模式,讓我們運(yùn)行看一下結(jié)果。
public class main {
public static void main(String[] args) {
Play feiZhai = new FeiZhai();
EbueProxy ebueProxy = new EbueProxy(feiZhai);
ebueProxy.lol();
}
}
商家接到訂單,開(kāi)始做飯
飯做完了,外賣小哥取到餐
B鍵已扣,不死不休!
外賣小哥進(jìn)行配送
叮咚!您好,您的餐,請(qǐng)慢用!
三、jdk 動(dòng)態(tài)代理
又是充滿希望的一天,今天我們的肥宅小兄弟不想玩LOL了,他熟練的拿起手機(jī),打開(kāi)和平精英,準(zhǔn)備吃(kuai)雞(di)之旅。
我們給接口類加一個(gè)方法
public interface Play {
public void lol();
public void chiJi();
}
再具體實(shí)現(xiàn)一下
public class FeiZhai implements Play {
public void lol() {
System.out.println("B鍵已扣,不死不休!");
}
public void chiJi() {
System.out.println("人體描邊說(shuō)的就是在下了!");
}
}
到飯點(diǎn)了,又是和昨天一樣的情況,我們?cè)賹懸惶状a,委托餓不餓APP幫我們送下餐
/**
* 餓不餓App
*/
public class EbueProxy implements Play {
//聲明目標(biāo)對(duì)象-為肥宅小兄弟服務(wù)
private FeiZhai feiZhai;
//通過(guò)構(gòu)造方法,獲取目標(biāo)對(duì)象,進(jìn)行賦值
public EbueProxy(Play play){
this.feiZhai = (FeiZhai) play;
}
//對(duì)原來(lái)的方法進(jìn)行擴(kuò)展
public void lol() {
System.out.println("商家接到訂單,開(kāi)始做飯");
System.out.println("飯做完了,外賣小哥取到餐");
//調(diào)用目標(biāo)對(duì)象的方法
//在不影響肥宅小兄弟玩LOL的情況下,進(jìn)行功能的拓展和增強(qiáng)。
feiZhai.lol();
System.out.println("外賣小哥進(jìn)行配送");
System.out.println("叮咚!您好,您的餐,請(qǐng)慢用!");
}
//對(duì)原來(lái)的方法進(jìn)行擴(kuò)展
public void chiJi() {
System.out.println("商家接到訂單,開(kāi)始做飯");
System.out.println("飯做完了,外賣小哥取到餐");
//調(diào)用目標(biāo)對(duì)象的方法
//在不影響肥宅小兄弟玩吃雞的情況下,進(jìn)行功能的拓展和增強(qiáng)。
feiZhai.chiJi();
System.out.println("外賣小哥進(jìn)行配送");
System.out.println("叮咚!您好,您的餐,請(qǐng)慢用!");
}
}
看到這里的小伙伴們,應(yīng)該都發(fā)現(xiàn)問(wèn)題了,如果肥宅小兄弟每天換個(gè)游戲,那我們豈不是要累死了,而且現(xiàn)在還只是1個(gè)肥宅小兄弟,如果是2個(gè)、3個(gè)、100個(gè)呢,難道我們要?jiǎng)?chuàng)建不同的 EbueProxy 代理類么?
要知道現(xiàn)在咱們代碼中的 EbueProxy 類,只為 FeiZhai 這個(gè)類提供代理服務(wù),相當(dāng)于定制版,如果我想為FeiZhai1、FeiZhai2提供代理服務(wù),我還要再寫2個(gè)代理類,如果這個(gè)時(shí)候Play接口新加了幾個(gè)方法,我們又要去維護(hù)實(shí)現(xiàn)了Play接口的這些類,很不方便。
本著最少的代碼干最多的事這種原則,我們不能接受這樣的結(jié)果。
這個(gè)時(shí)候我們就要使用 jdk動(dòng)態(tài)代理 了,我們創(chuàng)建一個(gè)類 EbueDynamicProxy,實(shí)現(xiàn) InvocationHandler 接口,重寫 invoke 方法。
/**
* 餓不餓App-動(dòng)態(tài)代理
*/
public class EbueDynamicProxy implements InvocationHandler {
//注意這里,我們換成了Object,意味著我們可以接受不同的目標(biāo)對(duì)象
private Object object;
//通過(guò)構(gòu)造方法,獲取目標(biāo)對(duì)象,進(jìn)行賦值
public EbueDynamicProxy(Object object){
this.object = object;
}
/**
*
* 每一個(gè)動(dòng)態(tài)代理類都必須要實(shí)現(xiàn)InvocationHandler這個(gè)接口,并且每個(gè)代理類的實(shí)例都關(guān)聯(lián)到了一個(gè)handler,
* 當(dāng)我們通過(guò)代理對(duì)象調(diào)用一個(gè)方法的時(shí)候,這個(gè)方法的調(diào)用就會(huì)被轉(zhuǎn)發(fā)為由InvocationHandler這個(gè)接口的 invoke 方法來(lái)進(jìn)行調(diào)用。
* InvocationHandler這個(gè)接口的唯一一個(gè)方法 invoke 方法:
* 該方法接收3個(gè)參數(shù):
* proxy: 指代我們所代理的那個(gè)目標(biāo)對(duì)象
* method: 指代的是我們所要調(diào)用目標(biāo)對(duì)象的某個(gè)方法的Method對(duì)象
* args: 指代的是調(diào)用目標(biāo)對(duì)象某個(gè)方法時(shí)接受的參數(shù)
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("商家接到訂單,開(kāi)始做飯");
System.out.println("飯做完了,外賣小哥取到餐");
Object obj = method.invoke(object, args);
System.out.println("外賣小哥進(jìn)行配送");
System.out.println("叮咚!您好,肥先生的餐,請(qǐng)慢用!\n");
return obj;
}
}
靜態(tài)代理和jdk動(dòng)態(tài)代理最大的區(qū)別就是,jdk動(dòng)態(tài)代理是jvm幫我們生成了代理類。
至于jvm怎么生成的我們目前不需要去操心,我們看看我們?cè)趺从|發(fā)去讓jvm生成。
public class main {
public static void main(String[] args) {
//創(chuàng)建一個(gè)目標(biāo)對(duì)象的實(shí)例
Play feizhai = new FeiZhai();
//創(chuàng)建一個(gè)與目標(biāo)對(duì)象相關(guān)聯(lián)的InvocationHandler
InvocationHandler ebueDynamicProxy = new EbueDynamicProxy(feizhai);
//獲取目標(biāo)對(duì)象的類加載器
ClassLoader classLoader = feizhai.getClass().getClassLoader();
//獲取目標(biāo)對(duì)象的所有接口
Class[] interfaces = feizhai.getClass().getInterfaces();
//Proxy.newProxyInstance:返回代理類的一個(gè)實(shí)例,返回后的代理類可以當(dāng)作被代理類使用
Play feiZhaiProxy = (Play) Proxy.newProxyInstance(classLoader, interfaces, ebueDynamicProxy);
//英雄聯(lián)盟
feiZhaiProxy.lol();
//吃雞
feiZhaiProxy.chiJi();
}
}
運(yùn)行一下看看輸出
商家接到訂單,開(kāi)始做飯
飯做完了,外賣小哥取到餐
B鍵已扣,不死不休!
外賣小哥進(jìn)行配送
叮咚!您好,您的餐,請(qǐng)慢用!
商家接到訂單,開(kāi)始做飯
飯做完了,外賣小哥取到餐
人體描邊說(shuō)的就是在下了!
外賣小哥進(jìn)行配送
叮咚!您好,您的餐,請(qǐng)慢用!
是不是很簡(jiǎn)單,接下來(lái)我們分析一下原理,知道怎么做還不夠,還得知道為什么能這么做。
通過(guò)靜態(tài)代理,我們很明顯可以看到,動(dòng)態(tài)代理是由靜態(tài)代理演化而來(lái),之前我們手寫代理類,有幾個(gè)目標(biāo)對(duì)象就寫幾個(gè)代理類,目標(biāo)對(duì)象里有幾個(gè)方法,代理類里就寫幾個(gè)方法,現(xiàn)在換成jdk動(dòng)態(tài)代理,我們就靠jvm來(lái)動(dòng)態(tài)生成代理類,我們只要需要實(shí)現(xiàn) InvocationHandler 接口,重寫 invoke 方法,添加上需要擴(kuò)展的功能代碼,然后交給jvm就可以了,jvm會(huì)幫我們?cè)诔绦蜻\(yùn)行時(shí)生成代理類,代理類的名稱為$Proxy0、$Proxy1、$Proxy2 …以此類推。
這個(gè)時(shí)候我們想看看jvm生成的代理類是什么樣的怎么辦?我們?cè)趍ain方法里面加上這樣一行代碼,這樣jvm生成的代理類就會(huì)出現(xiàn)在項(xiàng)目里的com.sun.proxy這個(gè)目錄下面了。
public class main {
public static void main(String[] args) {
//這行代碼的意思是將JDK動(dòng)態(tài)代理生成的class文件保存到本地
//作者本地jdk1.8親測(cè)可用,有人說(shuō)新版本的jdk用下面一行好使,不知道是多新的版本,如果有人用了發(fā)現(xiàn)沒(méi)生成,可以換下面這行代碼試試!
//System.getProperties().put("jdk.proxy.ProxyGenerator.saveGeneratedFiles", "true");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//創(chuàng)建一個(gè)目標(biāo)對(duì)象的實(shí)例
Play feizhai = new FeiZhai();
//創(chuàng)建一個(gè)與目標(biāo)對(duì)象相關(guān)聯(lián)的InvocationHandler
InvocationHandler ebueDynamicProxy = new EbueDynamicProxy(feizhai);
//獲取目標(biāo)對(duì)象的類加載器
ClassLoader classLoader = feizhai.getClass().getClassLoader();
//獲取目標(biāo)對(duì)象的所有接口
Class[] interfaces = feizhai.getClass().getInterfaces();
//Proxy.newProxyInstance:返回代理類的一個(gè)實(shí)例,返回后的代理類可以當(dāng)作被代理類使用
Play feiZhaiProxy = (Play) Proxy.newProxyInstance(classLoader, interfaces, ebueDynamicProxy);
//英雄聯(lián)盟
feiZhaiProxy.lol();
//吃雞
feiZhaiProxy.chiJi();
}
}
我們運(yùn)行一下,果然生成了,文件名也是$Proxy開(kāi)頭

我們?cè)倏纯碿lass文件里面的代碼是什么樣的
package com.sun.proxy;
import com.test.proxy.Play;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Play {
private static Method m1;
private static Method m2;
private static Method m0;
private static Method m3;
private static Method m4;
public $Proxy0(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void chiJi() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void lol() throws {
try {
super.h.invoke(this, m4, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.test.proxy.Play").getMethod("chiJi");
m4 = Class.forName("com.test.proxy.Play").getMethod("lol");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
我們看到在這個(gè)類里面,寫了一個(gè)靜態(tài)代碼塊,這里的m1、m2、m3等等,都是使用了反射找到了對(duì)應(yīng)的Method對(duì)象。
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
m3 = Class.forName("com.test.proxy.Play").getMethod("lol");
m4 = Class.forName("com.test.proxy.Play").getMethod("chiJi");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
然后我們?cè)僬业轿覀儗懙膌ol方法,我們拿這個(gè)進(jìn)行舉例說(shuō)明。
public final void lol() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以看到代碼很簡(jiǎn)單,主要就是這么一行代碼
super.h.invoke(this, m3, (Object[])null);
這行代碼的意思就是調(diào)用當(dāng)前類的父類的h變量的invoke方法。這么讀發(fā)現(xiàn)有點(diǎn)繞是不是,沒(méi)事,我們來(lái)剖析一下看看當(dāng)前類的父類是誰(shuí)?當(dāng)前類的父類的h變量又是什么?調(diào)用這個(gè)invoke方法又有什么用?來(lái)上代碼。
import com.test.proxy.Play;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class $Proxy0 extends Proxy implements Play
從上面這段代碼我們可以看到,$Proxy0代理類繼承了Proxy類,并且實(shí)現(xiàn)了我們寫的Play接口,這下明白了,當(dāng)前類的父類就是Proxy類。
public class Proxy implements java.io.Serializable {
private static final long serialVersionUID = -2222568056686623797L;
private static final Class<?>[] constructorParams = { InvocationHandler.class };
private static final WeakCache<ClassLoader, Class<?>[], Class<?>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
//看這里
protected InvocationHandler h;
}
再看上面這段代碼,h變量就是InvocationHandler類,那么super.h.invoke方法,就相當(dāng)于是調(diào)用了proxy的h變量的invoke方法,還記得invoke方法是什么嗎,就是我們寫的EbueDynamicProxy類里面的invoke方法,執(zhí)行的就是這個(gè)方法。至于什么時(shí)候把EbueDynamicProxy類賦值給了h變量,我們?cè)趍ain方法里不是寫了Proxy.newProxyInstance了,就是在那個(gè)時(shí)候,把EbueDynamicProxy傳過(guò)去的。
接下來(lái)我們來(lái)粗略看一下cglib動(dòng)態(tài)代理。
如果你使用了Spring框架,就不用單獨(dú)引入cglib了,因?yàn)镾pring集成了cglib。
接下來(lái)我們創(chuàng)建一個(gè)普通的類,寫上一個(gè)方法。
public class HelloFeiZhai {
public void hello(){
System.out.println("你好,肥宅!");
}
}
然后我們開(kāi)始為這個(gè)類創(chuàng)建動(dòng)態(tài)代理,首先要?jiǎng)?chuàng)建一個(gè)類,實(shí)現(xiàn)MethodInterceptor接口,并且重寫intercept方法。
public class CglibDynamicProxy implements MethodInterceptor {
/**
* 增強(qiáng)代碼
* @param object 被代理的對(duì)象
* @param method 代理的方法
* @param objects 方法參數(shù)
* @param methodProxy cglib方法代理對(duì)象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object object, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("你好,我叫 有趣的靈魂200斤");
Object obj = methodProxy.invokeSuper(object, objects);
System.out.println("我好你******");
return obj;
}
}
然后看看main方法
public static void main(String[] args) {
CglibDynamicProxy cglibDynamicProxy = new CglibDynamicProxy();
//Enhancer是cglib的字節(jié)碼增強(qiáng)器,用來(lái)創(chuàng)建動(dòng)態(tài)代理類
Enhancer enhancer = new Enhancer();
//指定要代理的業(yè)務(wù)類(即:為要生成的代理類指定父類)
enhancer.setSuperclass(HelloFeiZhai.class);
//設(shè)置回調(diào):對(duì)于代理類上所有方法的調(diào)用,都會(huì)調(diào)用CallBack,而Callback則需要實(shí)現(xiàn)MethodInterceptor接口的intercept()方法進(jìn)行攔截
enhancer.setCallback(cglibDynamicProxy);
// 創(chuàng)建動(dòng)態(tài)代理類對(duì)象并返回
HelloFeiZhai helloFeiZhai = (HelloFeiZhai)enhancer.create();
// 調(diào)用
helloFeiZhai.hello();
}
運(yùn)行結(jié)果:
你好,我叫 有趣的靈魂200斤
你好,肥宅!
我好你*****
四、cglib 動(dòng)態(tài)代理
cglib的源碼確實(shí)比較復(fù)雜,我自己還有好多地方不明白,就不拿出來(lái)誤導(dǎo)大家了,我粗略的說(shuō)一下原理:通過(guò)字節(jié)碼技術(shù)為一個(gè)類創(chuàng)建子類,并在子類中采用方法攔截的技術(shù)攔截所有父類方法的調(diào)用,順勢(shì)織入橫切邏輯。
五、總結(jié)
jdk動(dòng)態(tài)代理在老的java版本中,效率不盡人意,是比cglib要慢的,但是在jdk1.8版本,據(jù)大佬說(shuō),jdk動(dòng)態(tài)代理已經(jīng)是比cglib要快了。
在這篇文章中我們講了java設(shè)計(jì)模式之一的《代理模式》,介紹了現(xiàn)在java中的三種代理模式(靜態(tài)代理、jdk動(dòng)態(tài)代理、cglib動(dòng)態(tài)代理),而且分別演示了三種代理模式的使用方法、說(shuō)明了它們之間的關(guān)系和區(qū)別,以及優(yōu)缺點(diǎn)。
如果寫的有什么問(wèn)題,歡迎大家在底下評(píng)論指正,我會(huì)及時(shí)采納和修改,感謝!