您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關如何在Java中實現代理模式,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
1. 簡單,只需理解基本的概念,就可以編寫適合于各種情況的應用程序;2. 面向對象;3. 分布性,Java是面向網絡的語言;4. 魯棒性,java提供自動垃圾收集來進行內存管理,防止程序員在管理內存時容易產生的錯誤。;5. 安全性,用于網絡、分布環境下的Java必須防止病毒的入侵。6. 體系結構中立,只要安裝了Java運行時系統,就可在任意處理器上運行。7. 可移植性,Java可以方便地移植到網絡上的不同機器。8.解釋執行,Java解釋器直接對Java字節碼進行解釋執行。
代理模式就是有一個張三,別人都沒有辦法找到他,只有他的秘書可以找到他。那其他人想和張三交互,只能通過他的秘書來進行轉達交互。這個秘書就是代理者,他代理張三。
再看看另一個例子:賣房子
賣房子的步驟:
1.找買家
2.談價錢
3.簽合同
4.和房產局簽訂一些亂七八糟轉讓協議
一般賣家只在簽合同的時候可能出面一下,其他的1,2,4都由中介去做。那你問這樣有什么用呢?
首先,一個中介可以代理多個賣房子的賣家,其次,我們可以在不修改賣家的代碼的情況下,給他實現房子加價、打廣告等等夾帶私貨的功能。
而Java的代理模式又分為靜態代理和動態代理
靜態代理中存在著以下的角色:
抽象角色:一般使用接口或者抽象類實現(一般是真實角色和代理角色抽象出來的共同部分,比如賣房子的人和中介都有公共的方法賣房子)
真實角色:被代理的角色(表示一個具體的人,比如賣房子的張三)
代理角色:代理真實角色的中介,一般在代理真實角色后,會做一些附屬的操作
客戶:使用代理角色來進行一些操作(買房子的)
代碼實現:
//接口(抽象角色) 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+"男歌手在唱歌"); } }
歌手的經紀人
//代理角色 public class Agent implements Singer{ private MaleSinger singer; //代理角色要有一個被代理角色 public Agent(MaleSinger singer) { this.singer = singer; } @Override public void sing() { System.out.println("協商出場費,做會場工作"); //一定是被代理角色歌手去唱歌 singer.sing(); System.out.println("會場收拾,結算費用"); } }
客戶
//客戶 public class Client { public static void main(String[] args) { MaleSinger singer=new MaleSinger("周杰倫"); Agent agent=new Agent(singer); agent.sing();//通過代理來運行唱歌 } }
可以看到抽象角色就包含了具體角色和代理角色公共的方法sing()。然后通過歌手的經紀人在歌手唱歌的前后可以任意增加自己想要增加的代碼。從而達到不修改歌手類方法的同時給唱歌增加新功能的目的。
說白了。代理就是在不修改原來的代碼的情況下,給源代碼增強功能。
小結
靜態代理模式的主要優點有:
代理模式在客戶端與目標對象之間起到一個中介作用和保護目標對象的作用(經紀人保護周杰倫,外界不能直接接觸周杰倫)
代理對象可以擴展目標對象的功能(本來只能唱歌,現在又多了協商出場費,做會場工作等等功能)
代理模式能將客戶端與目標對象分離,在一定程度上降低了系統的耦合度,增加了程序的可擴展性
其主要缺點是:
代理模式會造成系統設計中類的數量增加(多了個代理類)
在客戶端和目標對象之間增加一個代理對象,會造成請求處理速度變慢(每次都要先找中介才能找到周杰倫)
增加了系統的復雜度(一開始只是個獨立的流浪歌手,然后有了經紀人后就十分復雜了)
靜態代理中,比如上述的例子,我們所寫的經紀人只能服務malesinger,不能再服務其他的類型的歌手,這很不現實。因為經紀人肯定能去服務不止一種歌手,甚至可能連歌手都不是,去服務跳舞的了。如果靜態代理中要實現這個結果,那我們要手動編寫好多個agent類,十分繁瑣而復雜。所以就出現了動態代理,動態代理可以自動生成代理人的代碼。
JDK原生的動態代理
核心類:InvocationHandler類
和Proxy類
我們重新寫一下Singer接口,給他多一個跳舞的方法
//歌手接口 public interface Singer2 { void sing(); void dance(); }
當然對應的男歌手實現類也要改變
//男歌手實現類 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");//設置用于輸出jdk動態代理產生的類 //簡單例子,把所有東西放到一段來解釋 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, 實現的接口,注意是個數組 //InvocationHandler h 處理函數) Singer2 agent = (Singer2) Proxy.newProxyInstance(Client2.class.getClassLoader(), new Class[]{Singer2.class}, new InvocationHandler() {//匿名內部類的方式實現InvocationHandler接口,對這個看不懂的可以參考https://blog.csdn.net/Doraemon_Nobita/article/details/115506705?spm=1001.2014.3001.5501 @Override // 這個invoke就是我們調用agent.sing()后調用的方法 // invoke(Object proxy, 代理對象 // Method method, method是方法,即我們要調用的方法(是唱歌還是跳舞,在調用的時候會是sing()還是dance()) // Object[] args 參數列表,可能你需要傳參) public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("協商出場費,做會場工作"); //關于invoke的講解,詳情可以參考:https://blog.csdn.net/Doraemon_Nobita/article/details/115702012?spm=1001.2014.3001.5501 調用指定的方法的那部分。 //invoke方法的參數,一個是Object類型,也就是調用該方法的對象, //第二個參數是一個可變參數類型,也就是給這個方法的傳參,外層的這個已經給我們封裝成args了,直接用就是了 Object invoke = method.invoke(maleSinger,args);//通過反射獲取到的method名我們再invoke激活一下,傳入要調用該方法的對象。這里用maleSinger System.out.println("會場收拾,結算費用"); return invoke; } }); agent.sing();//可以調用到maleSinger的sing() agent.dance();//調用到maleSinger的dance() System.out.println("實例2------------------------------------------------"); //這個簡單例子不行啊,我還每次必須寫死這里是maleSinger,以后想換別的還得改這里。動態代理豈是如此不便之物。 //所以我們直接實現一下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)); //可以看到現在代理人創建就十分方便了 agentJJ.dance(); agentJJ.sing(); agentJayZ.sing(); agentJayZ.dance(); } }
在第一個例子中,可以看到我們需要利用Proxy類的newProxyInstance()方法就可以生成一個代理對象。而newProxyInstance()的參數又有類加載器、實現的接口數組、以及InvocationHandler對象。在這里使用匿名內部類來實現InvocationHandler接口。實現該接口需要實現他的invoke方法,這個方法就是我們代理對象調用原方法的時候會使用到的方法。區別于反射中的invoke方法,它有三個參數分別是代理對象,調用的方法,方法的參數數組。這里代理對象我們不管,調用的方法則是通過反射獲取到的我們使用該代理調用sing()方法或者dance()方法的方法名。通過反射中的invoke方法,可以運行這個指定的對象里方法名的方法。
而第二個例子中,為了實現可以代理任何類,我們實現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("協商出場費,做會場工作"); //一定是歌手去唱歌 Object invoke = method.invoke(object,args); System.out.println("會場收拾,結算費用"); return invoke; } }
可以看出,這里和第一個例子的實現是差不多的,只不過我們使用Object類來代替了之前的寫死的MaleSinger類,這樣我們就可以代理任何的類型了,只要這個類型需要我們在前后加"協商出場費,做會場工作"、“會場收拾,結算費用”。那可以看到第二個例子中,林俊杰和周杰倫的代理人可以很方便地創建出來,哪怕后面再實現了一個FemaleSinger類,也可以直接生成他的代理人。
加了
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//設置用于輸出jdk動態代理產生的類
這句代碼以后,我們就可以在項目中找到JDK自動生成的代理類代碼:
打開可以看到就是自動生成的一段幫我們寫代理的方法。
可以看到就是調用了h.invoke,這個h就是我們傳參為InvocationHandler的對象,調用了我們自己寫的invoke方法。
cglib動態代理
我們需要在maven配置文件中導入相應的包。在pom.xml文件里增加如下代碼:
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
使用方法和JDK的動態代理類似,只是我們不需要再實現接口了,定義一個普通類CglibMaleSinger.java
public class CglibMaleSinger { public CglibMaleSinger(String name) { this.name = name; } private String name; public CglibMaleSinger() {//注意這里一定要有無參構造器,不然之后會報錯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);//設置其超類,我們要代理哪個類就傳哪個類 //MethodInterceptor是攔截器,就是把我的方法攔截住然后再去增強 enhancer.setCallback(new MethodInterceptor() {//設置方法攔截器 // o 是指被增強的對象,指自己 // method是攔截后的方法,把父類的方法攔截,增強后寫在了子類里 // objs 參數 // 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的動態代理使用方法基本一致,只是invoke方法變成了intercept方法而已。
加上System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"存儲路徑");
語句后,在你自己設置的存儲路徑下會出現一個包含生成的class文件的文件夾。
點開hj文件夾下的.class文件
可以看到是繼承了我們的CglibMaleSinger類,并且重寫了我們的方法,重寫內容中調用了intercept()方法。
關于如何在Java中實現代理模式就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。