Java-代理模式的理解
- 引言
设计模式是语言的表达方式,它能让语言轻便而富有内涵、易读却功能强大。代理模式在Java中十分常见,有为扩展某些类的功能而使用静态代理,也有如Spring实现AOP而使用动态代理,更有RPC实现中使用的调用端调用的代理服务。代理模型除了是一种设计模式之外,它更是一种思维,所以探讨并深入理解这种模型是非常有必要的。
- 代理模式拳谱总纲
代理模式这种设计模式是一种使用代理对象来执行目标对象的方法并在代理对象中增强目标对象方法的一种设计模式。代理对象代为执行目标对象的方法,并在此基础上进行相应的扩展。看起来是有点拗口,首先介绍一个原则:开闭原则(对扩展开放,对修改关闭)。一种好的设计模式甚至是架构,都是在不修改原有形态的基础上扩展出新的功能。
代理模式的元素是:共同接口、代理对象、目标对象。
代理模式的行为:由代理对象执行目标对象的方法、由代理对象扩展目标对象的方法。
代理模式的宏观特性:对客户端只暴露出接口,不暴露它以下的架构。
代理模式的微观特性:每个元由三个类构成,如图。
代理模式的种类:静态代理、动态代理(jdk动态代理、cglib动态代理、Spring和AspectJ实现的动态代理)
- 静态代理
静态代理模式就是如上图所示,构造三个类实现他们的关系。
首先会思考的一点就是为什么需要实现同一个接口,如果不实现同一个接口,一样可以“代理”功能,所以为什么非要实现同一个接口。我个人认为不实现统一接口的话代理方法有可能会不能完全实现(因为实现接口必须实现它的抽象方法),其次就是方法名称了,已经由接口定义的方法就是目标对象实现了的功能,也算是一种提醒,最后我能想到的就是不实现统一接口的话应该叫做聚合而不是代理。
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{ @Override public void add() { System.out.println("在目标对象中执行add"); } @Override public void delete() { System.out.println("在目标对象中执行delete"); } @Override public void update() { System.out.println("在目标对象中执行update"); } @Override public void query() { System.out.println("在目标对象中执行query"); } }
package Proxy.Static; /** * 代理对象 * @author ctk * */ public class UserDaoProxy implements DAOInterface{ UserDao userDao = null; public UserDaoProxy(UserDao userDao){ this.userDao = userDao; } @Override public void add() { userDao.add(); System.out.println("记录日志add"); } @Override public void delete() { userDao.delete(); System.out.println("记录日志delete"); } @Override public void update() { userDao.update(); System.out.println("记录日志update"); } @Override public void query() { userDao.query(); System.out.println("记录日志query"); } }
静态代理就是写死了在代理对象中执行这个方法前后执行添加功能的形式,每次要在接口中添加一个新方法,则需要在目标对象中实现这个方法,并且在代理对象中实现相应的代理方法,幸而Java有独特的反射技术,可以实现动态代理。
- 动态代理
实际上在原理上讲我只认识Jdk动态代理和Cglib动态代理,Spring和AspectJ的动态代理是基于前面两种来实现的。
Jdk的动态代理,是使用反射技术获得类的加载器并且创建实例,根据类执行的方法在执行方法的前后发送通知。
接口和实现类还是使用上面贴出来的。
在代理对象Proxy的新建代理实例方法中,必须要获得类的加载器、类所实现的接口、还有一个拦截方法的句柄。
在句柄的invoke中如果不调用method.invoke则方法不会执行。在invoke前后添加通知,就是对原有类进行功能扩展了。
创建好代理对象之后,proxy可以调用接口中定义的所有方法,因为它们实现了同一个接口,并且接口的方法实现类的加载器已经被反射框架获取到了。
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() { //回调方法 拦截到目标对象的时候执行 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("在 代理对象 中拦截到:"+method.getName()); Object o = method.invoke(userDao, args);//调用拦截到的方法 return o; } }); proxy.delete(); } }
另一种动态代理的实现方式是使用Cglib,所以得先导入两个包
实现方式如下
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 代理对象继承目标对象 * * @author ctk * */ public class Test { public static void main(String[] args) { UserDao target = new UserDao(); Enhancer en = new Enhancer(); //设置代理对象的父类 en.setSuperclass(target.getClass()); //设置回调 en.setCallback(new MethodInterceptor() { @Override public Object intercept(Object arg0, Method arg1, Object[] arg2, MethodProxy arg3) throws Throwable { System.out.println("在 代理对象 中拦截到 "+arg1.getName()); Object obj = arg1.invoke(target, arg2); return obj; } }); UserDao proxy = (UserDao) en.create(); proxy.add(); } }
Cglib实现代理的方式是和目标对象使用同一个父类,无论是继承还是实现接口,都是为了代理对象能直接调用目标对象的方法。
- RPC服务分离代理模式
在分布式的书上看到RPC的诞生契机,任何一种技术干学都是不长进的,只有把它的前世今生给摸透了,才能有所领悟。当你的web应用达到了编译一次1分钟或者5分钟或者更多的时候,你就该想到服务解耦这个办法了。即使是在做一个ERP,但是业务多起来也是如C++编译一样的(不是黑C++哈哈),服务解耦的意义就是想,我这个web应用调用的service能不能不运行在同一个web应用中呢?RPC由此诞生了,本地保存一个调用列表,而真正执行是在另外一个应用(或者另外一个服务器)。只要制定好通信框架序列化和反序列化的制度(私有协议栈),就做成了一个RPC框架。虽然如ActiveMQ之流的通信框架底层使用的是这种技术,但是它们的复杂程度也不是一个demo能说清楚的。
服务调用方
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() { @Override public 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(); } } }); } }
服务调用
package RPC; import java.io.IOException; import java.net.InetSocketAddress; /** * 测试RPCdemo * @author ctk * */ public class RPCTest { public static void main(String[] args) { new Thread(new Runnable() { @Override public 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")); } }
服务列表用接口来实现再好不过了,在调用importer这个方法的时候,会往提供方请求一个执行服务的类和方法并要求返回一个执行结果。
服务提供方
package RPC; /** * RPC服务代理接口 * @author MacBook * */ public interface EchoService { public String echo(String ping); }
package RPC; public class EchoServiceImpl implements EchoService{ @Override public 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; } @Override public 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(); } } } }
服务器架构和BIO的多线程结构没有什么区别,只是使用了线程池而已,把accept到的socket交给线程实现类去完成任务。在提供服务的时候先读取请求的对象名和方法名,并通过反射创建实例,最后接受到参数,运行结束之后返回result结果。
- 一些感想
以前觉得,设计模式只是为了代码更加简洁,或者实现功能更为有条理而设置的,现在慢慢感悟到框架的设计也可设计模式有紧密的关系。就比如生产者消费者模型和消息中间件的架构是密切相关的,相似的还有观察者模型、发布订阅模型。而RPC服务实现服务解耦也有点像代理模式的思想,虽然它没有使用反射进行动态代理,没有AOP那么直接。还有一点,就是不同场景下的设计模式需要做不同的思考,比如单例模式在高并发环境下应该如何达到高效、安全。
多思考,多挖掘,干巴爹。