Java設(shè)計模式之代理模式詳解
一、代理模式
代理模式就是有一個張三,別人都沒有辦法找到他,只有他的秘書可以找到他。那其他人想和張三交互,只能通過他的秘書來進行轉(zhuǎn)達交互。這個秘書就是代理者,他代理張三。
再看看另一個例子:賣房子
賣房子的步驟:
1.找買家
2.談價錢
3.簽合同
4.和房產(chǎn)局簽訂一些亂七八糟轉(zhuǎn)讓協(xié)議
一般賣家只在簽合同的時候可能出面一下,其他的1,2,4都由中介去做。那你問這樣有什么用呢?
首先,一個中介可以代理多個賣房子的賣家,其次,我們可以在不修改賣家的代碼的情況下,給他實現(xiàn)房子加價、打廣告等等夾帶私貨的功能。
而Java的代理模式又分為靜態(tài)代理和動態(tài)代理
二、靜態(tài)代理
靜態(tài)代理中存在著以下的角色:
- 抽象角色:一般使用接口或者抽象類實現(xiàn)(一般是真實角色和代理角色抽象出來的共同部分,比如賣房子的人和中介都有公共的方法賣房子)
- 真實角色:被代理的角色(表示一個具體的人,比如賣房子的張三)
- 代理角色:代理真實角色的中介,一般在代理真實角色后,會做一些附屬的操作
- 客戶:使用代理角色來進行一些操作(買房子的)
代碼實現(xiàn):
//接口(抽象角色)
public interface Singer{
// 歌星要會唱歌
void sing();
}
實體類男歌手
//具體角色,男歌手
public class MaleSinger implements Singer{
private String name;
public MaleSinger(String name) {
this.name = name;
}
@Override
public void sing() {
System.out.println(this.name+"男歌手在唱歌");
}
}
歌手的經(jīng)紀人
//代理角色
public class Agent implements Singer{
private MaleSinger singer; //代理角色要有一個被代理角色
public Agent(MaleSinger singer) {
this.singer = singer;
}
@Override
public void sing() {
System.out.println("協(xié)商出場費,做會場工作");
//一定是被代理角色歌手去唱歌
singer.sing();
System.out.println("會場收拾,結(jié)算費用");
}
}
客戶
//客戶
public class Client {
public static void main(String[] args) {
MaleSinger singer=new MaleSinger("周杰倫");
Agent agent=new Agent(singer);
agent.sing();//通過代理來運行唱歌
}
}
可以看到抽象角色就包含了具體角色和代理角色公共的方法sing()。然后通過歌手的經(jīng)紀人在歌手唱歌的前后可以任意增加自己想要增加的代碼。從而達到不修改歌手類方法的同時給唱歌增加新功能的目的。
說白了。代理就是在不修改原來的代碼的情況下,給源代碼增強功能。
小結(jié)
靜態(tài)代理模式的主要優(yōu)點有:
- 代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用(經(jīng)紀人保護周杰倫,外界不能直接接觸周杰倫)
- 代理對象可以擴展目標對象的功能(本來只能唱歌,現(xiàn)在又多了協(xié)商出場費,做會場工作等等功能)
- 代理模式能將客戶端與目標對象分離,在一定程度上降低了系統(tǒng)的耦合度,增加了程序的可擴展性
其主要缺點是:
- 代理模式會造成系統(tǒng)設(shè)計中類的數(shù)量增加(多了個代理類)
- 在客戶端和目標對象之間增加一個代理對象,會造成請求處理速度變慢(每次都要先找中介才能找到周杰倫)
- 增加了系統(tǒng)的復(fù)雜度(一開始只是個獨立的流浪歌手,然后有了經(jīng)紀人后就十分復(fù)雜了)
三、動態(tài)代理
靜態(tài)代理中,比如上述的例子,我們所寫的經(jīng)紀人只能服務(wù)malesinger,不能再服務(wù)其他的類型的歌手,這很不現(xiàn)實。因為經(jīng)紀人肯定能去服務(wù)不止一種歌手,甚至可能連歌手都不是,去服務(wù)跳舞的了。如果靜態(tài)代理中要實現(xiàn)這個結(jié)果,那我們要手動編寫好多個agent類,十分繁瑣而復(fù)雜。所以就出現(xiàn)了動態(tài)代理,動態(tài)代理可以自動生成代理人的代碼。
JDK原生的動態(tài)代理
核心類:InvocationHandler類和Proxy類
我們重新寫一下Singer接口,給他多一個跳舞的方法
//歌手接口
public interface Singer2 {
void sing();
void dance();
}
當然對應(yīng)的男歌手實現(xiàn)類也要改變
//男歌手實現(xiàn)類
public class MaleSinger2 implements Singer2 {
private String name;
public MaleSinger2(String name) {
this.name = name;
}
@Override
public void sing() {
System.out.println(this.name+"在唱歌");
}
@Override
public void dance() {
System.out.println(this.name+"在跳舞");
}
}
然后我們直接進入客戶,測試。
import com.hj.Agent2;
import com.hj.MaleSinger2;
import com.hj.Singer2;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client2 {
public static void main(String[] args) {
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//設(shè)置用于輸出jdk動態(tài)代理產(chǎn)生的類
//簡單例子,把所有東西放到一段來解釋
System.out.println("實例1------------------------------------------------");
MaleSinger2 maleSinger = new MaleSinger2("周杰倫");
//新建代理實例
//newProxyInstance(ClassLoader loader, 類加載器,不懂的可以去看https://blog.csdn.net/Doraemon_Nobita/article/details/115702012?spm=1001.2014.3001.5501
//Class<?>[] interfaces, 實現(xiàn)的接口,注意是個數(shù)組
//InvocationHandler h 處理函數(shù))
Singer2 agent = (Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),
new Class[]{Singer2.class}, new InvocationHandler() {//匿名內(nèi)部類的方式實現(xiàn)InvocationHandler接口,對這個看不懂的可以參考https://blog.csdn.net/Doraemon_Nobita/article/details/115506705?spm=1001.2014.3001.5501
@Override
// 這個invoke就是我們調(diào)用agent.sing()后調(diào)用的方法
// invoke(Object proxy, 代理對象
// Method method, method是方法,即我們要調(diào)用的方法(是唱歌還是跳舞,在調(diào)用的時候會是sing()還是dance())
// Object[] args 參數(shù)列表,可能你需要傳參)
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("協(xié)商出場費,做會場工作");
//關(guān)于invoke的講解,詳情可以參考:https://blog.csdn.net/Doraemon_Nobita/article/details/115702012?spm=1001.2014.3001.5501 調(diào)用指定的方法的那部分。
//invoke方法的參數(shù),一個是Object類型,也就是調(diào)用該方法的對象,
//第二個參數(shù)是一個可變參數(shù)類型,也就是給這個方法的傳參,外層的這個已經(jīng)給我們封裝成args了,直接用就是了
Object invoke = method.invoke(maleSinger,args);//通過反射獲取到的method名我們再invoke激活一下,傳入要調(diào)用該方法的對象。這里用maleSinger
System.out.println("會場收拾,結(jié)算費用");
return invoke;
}
});
agent.sing();//可以調(diào)用到maleSinger的sing()
agent.dance();//調(diào)用到maleSinger的dance()
System.out.println("實例2------------------------------------------------");
//這個簡單例子不行啊,我還每次必須寫死這里是maleSinger,以后想換別的還得改這里。動態(tài)代理豈是如此不便之物。
//所以我們直接實現(xiàn)一下InvocationHandler接口,取名為Agent2
MaleSinger2 JayZ=new MaleSinger2("周杰倫");
MaleSinger2 JJ =new MaleSinger2("林俊杰");
Singer2 agentJJ=(Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),
new Class[]{Singer2.class}, new Agent2(JJ));
Singer2 agentJayZ=(Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(),
new Class[]{Singer2.class}, new Agent2(JayZ));
//可以看到現(xiàn)在代理人創(chuàng)建就十分方便了
agentJJ.dance();
agentJJ.sing();
agentJayZ.sing();
agentJayZ.dance();
}
}
在第一個例子中,可以看到我們需要利用Proxy類的newProxyInstance()方法就可以生成一個代理對象。而newProxyInstance()的參數(shù)又有類加載器、實現(xiàn)的接口數(shù)組、以及InvocationHandler對象。在這里使用匿名內(nèi)部類來實現(xiàn)InvocationHandler接口。實現(xiàn)該接口需要實現(xiàn)他的invoke方法,這個方法就是我們代理對象調(diào)用原方法的時候會使用到的方法。區(qū)別于反射中的invoke方法,它有三個參數(shù)分別是代理對象,調(diào)用的方法,方法的參數(shù)數(shù)組。這里代理對象我們不管,調(diào)用的方法則是通過反射獲取到的我們使用該代理調(diào)用sing()方法或者dance()方法的方法名。通過反射中的invoke方法,可以運行這個指定的對象里方法名的方法。
而第二個例子中,為了實現(xiàn)可以代理任何類,我們實現(xiàn)InvocationHandler接口,并把取類名為Agent2。下面是Agent2的代碼。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class Agent2 implements InvocationHandler {
private Object object;//想代理誰都可以,隨便啊
public Agent2(Object object) {
this.object = object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("協(xié)商出場費,做會場工作");
//一定是歌手去唱歌
Object invoke = method.invoke(object,args);
System.out.println("會場收拾,結(jié)算費用");
return invoke;
}
}
可以看出,這里和第一個例子的實現(xiàn)是差不多的,只不過我們使用Object類來代替了之前的寫死的MaleSinger類,這樣我們就可以代理任何的類型了,只要這個類型需要我們在前后加"協(xié)商出場費,做會場工作"、“會場收拾,結(jié)算費用”。那可以看到第二個例子中,林俊杰和周杰倫的代理人可以很方便地創(chuàng)建出來,哪怕后面再實現(xiàn)了一個FemaleSinger類,也可以直接生成他的代理人。
加了
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//設(shè)置用于輸出jdk動態(tài)代理產(chǎn)生的類這句代碼以后,我們就可以在項目中找到JDK自動生成的代理類代碼:

打開可以看到就是自動生成的一段幫我們寫代理的方法。

可以看到就是調(diào)用了h.invoke,這個h就是我們傳參為InvocationHandler的對象,調(diào)用了我們自己寫的invoke方法。
cglib動態(tài)代理
我們需要在maven配置文件中導(dǎo)入相應(yīng)的包。在pom.xml文件里增加如下代碼:
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>
</dependencies>
使用方法和JDK的動態(tài)代理類似,只是我們不需要再實現(xiàn)接口了,定義一個普通類CglibMaleSinger.java
public class CglibMaleSinger {
public CglibMaleSinger(String name) {
this.name = name;
}
private String name;
public CglibMaleSinger() {//注意這里一定要有無參構(gòu)造器,不然之后會報錯Superclass has no null constructors but no arguments were given
}
public void sing(){
System.out.println(this.name+"要去唱歌了");
}
public void dance(){
System.out.println(this.name+"要去跳舞了");
}
}
然后直接在客戶端測試:
import com.hj.CglibMaleSinger;
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CglibClient {
public static void main(String[] args) {
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"./class");//用于輸出生成的代理class文件,"./class"表示存儲在class文件夾中
CglibMaleSinger JayZ=new CglibMaleSinger("周杰倫");
Enhancer enhancer = new Enhancer();//定義一個增強器enhancer
enhancer.setSuperclass(CglibMaleSinger.class);//設(shè)置其超類,我們要代理哪個類就傳哪個類
//MethodInterceptor是攔截器,就是把我的方法攔截住然后再去增強
enhancer.setCallback(new MethodInterceptor() {//設(shè)置方法攔截器
// o 是指被增強的對象,指自己
// method是攔截后的方法,把父類的方法攔截,增強后寫在了子類里
// objs 參數(shù)
// methodProxy 父類的方法(攔截前的方法對象)
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("談出場費");
method.invoke(JayZ,objects);
System.out.println("出場費談完了");
return null;
}
});
CglibMaleSinger cglibMaleSinger = (CglibMaleSinger)enhancer.create();
cglibMaleSinger.sing();
cglibMaleSinger.dance();
}
}
和JDK的動態(tài)代理使用方法基本一致,只是invoke方法變成了intercept方法而已。
加上System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"存儲路徑");語句后,在你自己設(shè)置的存儲路徑下會出現(xiàn)一個包含生成的class文件的文件夾。

點開hj文件夾下的.class文件

可以看到是繼承了我們的CglibMaleSinger類,并且重寫了我們的方法,重寫內(nèi)容中調(diào)用了intercept()方法。

小結(jié)
Java動態(tài)代理只能夠?qū)涌谶M行代理,不能對普通類進行代理(因為所有生成的代理類的父類為Proxy,java不支持多重繼承)CGLIB可以代理普通類Java動態(tài)代理使用Java原生的反射API進行操作,在生成類上比較高效;而CGLIB使用ASM框架直接對字節(jié)碼(.class)改了,所以運行的時候是要比Java原生的效率要高些。
到此這篇關(guān)于Java設(shè)計模式之代理模式詳解的文章就介紹到這了,更多相關(guān)Java代理模式內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot請求參數(shù)傳遞與接收說明小結(jié)
這篇文章主要介紹了SpringBoot請求參數(shù)傳遞與接收,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-12-12
SpringBoot如何配置文件properties和yml
這篇文章主要介紹了SpringBoot如何配置文件properties和yml問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-08-08
java并發(fā)編程包JUC線程同步CyclicBarrier語法示例
這篇文章主要為大家介紹了java并發(fā)編程工具包JUC線程同步CyclicBarrier語法使用示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-03-03

