Java-代理模式的理解
- 引言
設(shè)計(jì)模式是語(yǔ)言的表達(dá)方式,它能讓語(yǔ)言輕便而富有內(nèi)涵、易讀卻功能強(qiáng)大。代理模式在Java中十分常見(jiàn),有為擴(kuò)展某些類的功能而使用靜態(tài)代理,也有如Spring實(shí)現(xiàn)AOP而使用動(dòng)態(tài)代理,更有RPC實(shí)現(xiàn)中使用的調(diào)用端調(diào)用的代理服務(wù)。代理模型除了是一種設(shè)計(jì)模式之外,它更是一種思維,所以探討并深入理解這種模型是非常有必要的。
- 代理模式拳譜總綱
代理模式這種設(shè)計(jì)模式是一種使用代理對(duì)象來(lái)執(zhí)行目標(biāo)對(duì)象的方法并在代理對(duì)象中增強(qiáng)目標(biāo)對(duì)象方法的一種設(shè)計(jì)模式。代理對(duì)象代為執(zhí)行目標(biāo)對(duì)象的方法,并在此基礎(chǔ)上進(jìn)行相應(yīng)的擴(kuò)展。看起來(lái)是有點(diǎn)拗口,首先介紹一個(gè)原則:開(kāi)閉原則(對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉)。一種好的設(shè)計(jì)模式甚至是架構(gòu),都是在不修改原有形態(tài)的基礎(chǔ)上擴(kuò)展出新的功能。
代理模式的元素是:共同接口、代理對(duì)象、目標(biāo)對(duì)象。
代理模式的行為:由代理對(duì)象執(zhí)行目標(biāo)對(duì)象的方法、由代理對(duì)象擴(kuò)展目標(biāo)對(duì)象的方法。
代理模式的宏觀特性:對(duì)客戶端只暴露出接口,不暴露它以下的架構(gòu)。
代理模式的微觀特性:每個(gè)元由三個(gè)類構(gòu)成,如圖。
? ? ? ? ? ? ? ? ? ?
代理模式的種類:靜態(tài)代理、動(dòng)態(tài)代理(jdk動(dòng)態(tài)代理、cglib動(dòng)態(tài)代理、Spring和AspectJ實(shí)現(xiàn)的動(dòng)態(tài)代理)
- 靜態(tài)代理
靜態(tài)代理模式就是如上圖所示,構(gòu)造三個(gè)類實(shí)現(xiàn)他們的關(guān)系。
首先會(huì)思考的一點(diǎn)就是為什么需要實(shí)現(xiàn)同一個(gè)接口,如果不實(shí)現(xiàn)同一個(gè)接口,一樣可以“代理”功能,所以為什么非要實(shí)現(xiàn)同一個(gè)接口。我個(gè)人認(rèn)為不實(shí)現(xiàn)統(tǒng)一接口的話代理方法有可能會(huì)不能完全實(shí)現(xiàn)(因?yàn)閷?shí)現(xiàn)接口必須實(shí)現(xiàn)它的抽象方法),其次就是方法名稱了,已經(jīng)由接口定義的方法就是目標(biāo)對(duì)象實(shí)現(xiàn)了的功能,也算是一種提醒,最后我能想到的就是不實(shí)現(xiàn)統(tǒng)一接口的話應(yīng)該叫做聚合而不是代理。
?
package Proxy.Static;public interface DAOInterface {public void add();public void delete();public void update();public void query(); }?
package Proxy.Static;public class UserDao implements DAOInterface{@Overridepublic void add() {System.out.println("在目標(biāo)對(duì)象中執(zhí)行add");}@Overridepublic void delete() {System.out.println("在目標(biāo)對(duì)象中執(zhí)行delete");}@Overridepublic void update() {System.out.println("在目標(biāo)對(duì)象中執(zhí)行update");}@Overridepublic void query() {System.out.println("在目標(biāo)對(duì)象中執(zhí)行query");}} package Proxy.Static; /*** 代理對(duì)象* @author ctk**/ public class UserDaoProxy implements DAOInterface{UserDao userDao = null;public UserDaoProxy(UserDao userDao){this.userDao = userDao;}@Overridepublic void add() {userDao.add();System.out.println("記錄日志add");}@Overridepublic void delete() {userDao.delete();System.out.println("記錄日志delete");}@Overridepublic void update() {userDao.update();System.out.println("記錄日志update");}@Overridepublic void query() {userDao.query();System.out.println("記錄日志query");}}靜態(tài)代理就是寫死了在代理對(duì)象中執(zhí)行這個(gè)方法前后執(zhí)行添加功能的形式,每次要在接口中添加一個(gè)新方法,則需要在目標(biāo)對(duì)象中實(shí)現(xiàn)這個(gè)方法,并且在代理對(duì)象中實(shí)現(xiàn)相應(yīng)的代理方法,幸而Java有獨(dú)特的反射技術(shù),可以實(shí)現(xiàn)動(dòng)態(tài)代理。
- 動(dòng)態(tài)代理
實(shí)際上在原理上講我只認(rèn)識(shí)Jdk動(dòng)態(tài)代理和Cglib動(dòng)態(tài)代理,Spring和AspectJ的動(dòng)態(tài)代理是基于前面兩種來(lái)實(shí)現(xiàn)的。
Jdk的動(dòng)態(tài)代理,是使用反射技術(shù)獲得類的加載器并且創(chuàng)建實(shí)例,根據(jù)類執(zhí)行的方法在執(zhí)行方法的前后發(fā)送通知。
接口和實(shí)現(xiàn)類還是使用上面貼出來(lái)的。
在代理對(duì)象Proxy的新建代理實(shí)例方法中,必須要獲得類的加載器、類所實(shí)現(xiàn)的接口、還有一個(gè)攔截方法的句柄。
在句柄的invoke中如果不調(diào)用method.invoke則方法不會(huì)執(zhí)行。在invoke前后添加通知,就是對(duì)原有類進(jìn)行功能擴(kuò)展了。
創(chuàng)建好代理對(duì)象之后,proxy可以調(diào)用接口中定義的所有方法,因?yàn)樗鼈儗?shí)現(xiàn)了同一個(gè)接口,并且接口的方法實(shí)現(xiàn)類的加載器已經(jīng)被反射框架獲取到了。
package Proxy.jdkProxy;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy;public class Test {public static void main(String[] args) {DAOInterface userDao = new UserDao();DAOInterface proxy = (DAOInterface) Proxy.newProxyInstance(userDao.getClass().getClassLoader(), userDao.getClass().getInterfaces(), new InvocationHandler() {//回調(diào)方法 攔截到目標(biāo)對(duì)象的時(shí)候執(zhí)行 @Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("在 代理對(duì)象 中攔截到:"+method.getName());Object o = method.invoke(userDao, args);//調(diào)用攔截到的方法return o;}});proxy.delete();} }? 另一種動(dòng)態(tài)代理的實(shí)現(xiàn)方式是使用Cglib,所以得先導(dǎo)入兩個(gè)包
實(shí)現(xiàn)方式如下
package Proxy.Cglib;import java.lang.reflect.Method;import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy;/*** cglib代理 要求類不能使final 代理對(duì)象繼承目標(biāo)對(duì)象* * @author ctk**/ public class Test {public static void main(String[] args) {UserDao target = new UserDao();Enhancer en = new Enhancer();//設(shè)置代理對(duì)象的父類 en.setSuperclass(target.getClass());//設(shè)置回調(diào)en.setCallback(new MethodInterceptor() {@Overridepublic Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable {System.out.println("在 代理對(duì)象 中攔截到 "+arg1.getName());Object obj = arg1.invoke(target, arg2);return obj;}});UserDao proxy = (UserDao) en.create();proxy.add();} }Cglib實(shí)現(xiàn)代理的方式是和目標(biāo)對(duì)象使用同一個(gè)父類,無(wú)論是繼承還是實(shí)現(xiàn)接口,都是為了代理對(duì)象能直接調(diào)用目標(biāo)對(duì)象的方法。
- RPC服務(wù)分離代理模式
在分布式的書上看到RPC的誕生契機(jī),任何一種技術(shù)干學(xué)都是不長(zhǎng)進(jìn)的,只有把它的前世今生給摸透了,才能有所領(lǐng)悟。當(dāng)你的web應(yīng)用達(dá)到了編譯一次1分鐘或者5分鐘或者更多的時(shí)候,你就該想到服務(wù)解耦這個(gè)辦法了。即使是在做一個(gè)ERP,但是業(yè)務(wù)多起來(lái)也是如C++編譯一樣的(不是黑C++哈哈),服務(wù)解耦的意義就是想,我這個(gè)web應(yīng)用調(diào)用的service能不能不運(yùn)行在同一個(gè)web應(yīng)用中呢?RPC由此誕生了,本地保存一個(gè)調(diào)用列表,而真正執(zhí)行是在另外一個(gè)應(yīng)用(或者另外一個(gè)服務(wù)器)。只要制定好通信框架序列化和反序列化的制度(私有協(xié)議棧),就做成了一個(gè)RPC框架。雖然如ActiveMQ之流的通信框架底層使用的是這種技術(shù),但是它們的復(fù)雜程度也不是一個(gè)demo能說(shuō)清楚的。
服務(wù)調(diào)用方
package RPC;import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.InetSocketAddress; import java.net.Socket;public class RPCimporter<S> {@SuppressWarnings("unchecked")public S importer(final Class<?> serviceClass,final InetSocketAddress addr){return (S)Proxy.newProxyInstance(serviceClass.getClassLoader(), new Class<?>[]{serviceClass.getInterfaces()[0]}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Socket socket = null;ObjectOutputStream out = null;ObjectInputStream ins = null;try{socket = new Socket();socket.connect(addr);out = new ObjectOutputStream(socket.getOutputStream());out.writeUTF(serviceClass.getName());out.writeUTF(method.getName());out.writeObject(method.getParameterTypes());out.writeObject(args);ins = new ObjectInputStream(socket.getInputStream());return ins.readObject();}finally {if(out != null)out.close();if(ins != null)ins.close();if(socket != null)socket.close();}}});} }服務(wù)調(diào)用
package RPC;import java.io.IOException; import java.net.InetSocketAddress;/*** 測(cè)試RPCdemo* @author ctk**/ public class RPCTest {public static void main(String[] args) {new Thread(new Runnable() {@Overridepublic void run() {try {RPCExporter.exporter("localhost", 10000);} catch (IOException e) {e.printStackTrace();}}}).start();RPCimporter<EchoService> importer = new RPCimporter<>();EchoService echo = importer.importer(EchoServiceImpl.class, new InetSocketAddress("localhost", 10000));System.out.println(echo.echo("hello world"));} }服務(wù)列表用接口來(lái)實(shí)現(xiàn)再好不過(guò)了,在調(diào)用importer這個(gè)方法的時(shí)候,會(huì)往提供方請(qǐng)求一個(gè)執(zhí)行服務(wù)的類和方法并要求返回一個(gè)執(zhí)行結(jié)果。
服務(wù)提供方
package RPC; /*** RPC服務(wù)代理接口* @author MacBook**/ public interface EchoService {public String echo(String ping); } package RPC;public class EchoServiceImpl implements EchoService{@Overridepublic String echo(String ping) {return ping != null ? ping + " ---> I am ok.":"I am ok";}} package RPC;import java.io.IOException; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.util.concurrent.Executor; import java.util.concurrent.Executors;public class RPCExporter {static Executor executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());public static void exporter(String hostName,int port) throws IOException{ServerSocket server = new ServerSocket();server.bind(new InetSocketAddress(hostName, port));try{while(true){executor.execute(new ExportTask(server.accept()));}}catch (Exception e) {} finally {server.close();}}} package RPC;import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Method; import java.net.Socket;public class ExportTask implements Runnable {Socket client = null;public ExportTask(Socket socket){client = socket;}@Overridepublic void run() {ObjectInputStream oins = null;ObjectOutputStream oous = null;try{oins = new ObjectInputStream(client.getInputStream());String interfaceName = oins.readUTF();Class<?> service = Class.forName(interfaceName);String methodName = oins.readUTF();Class<?>[] parameterTypes = (Class<?>[])oins.readObject();Object[] arguments = (Object[])oins.readObject();Method method = service.getMethod(methodName, parameterTypes);Object result = method.invoke(service.newInstance(), arguments);oous = new ObjectOutputStream(client.getOutputStream());oous.writeObject(result);}catch (Exception e) {}finally {try {if(oins != null)oins.close();if(oous != null)oous.close();if(client != null)client.close();} catch (IOException e) {e.printStackTrace();}}}}服務(wù)器架構(gòu)和BIO的多線程結(jié)構(gòu)沒(méi)有什么區(qū)別,只是使用了線程池而已,把a(bǔ)ccept到的socket交給線程實(shí)現(xiàn)類去完成任務(wù)。在提供服務(wù)的時(shí)候先讀取請(qǐng)求的對(duì)象名和方法名,并通過(guò)反射創(chuàng)建實(shí)例,最后接受到參數(shù),運(yùn)行結(jié)束之后返回result結(jié)果。
- 一些感想
以前覺(jué)得,設(shè)計(jì)模式只是為了代碼更加簡(jiǎn)潔,或者實(shí)現(xiàn)功能更為有條理而設(shè)置的,現(xiàn)在慢慢感悟到框架的設(shè)計(jì)也可設(shè)計(jì)模式有緊密的關(guān)系。就比如生產(chǎn)者消費(fèi)者模型和消息中間件的架構(gòu)是密切相關(guān)的,相似的還有觀察者模型、發(fā)布訂閱模型。而RPC服務(wù)實(shí)現(xiàn)服務(wù)解耦也有點(diǎn)像代理模式的思想,雖然它沒(méi)有使用反射進(jìn)行動(dòng)態(tài)代理,沒(méi)有AOP那么直接。還有一點(diǎn),就是不同場(chǎng)景下的設(shè)計(jì)模式需要做不同的思考,比如單例模式在高并發(fā)環(huán)境下應(yīng)該如何達(dá)到高效、安全。
多思考,多挖掘,干巴爹。
轉(zhuǎn)載于:https://www.cnblogs.com/chentingk/p/6433372.html
總結(jié)
以上是生活随笔為你收集整理的Java-代理模式的理解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: js 预编译 解释执行 作用域链 闭包
- 下一篇: Java中关于枚举的7种用法