代理模式
代理模式为对象提供一个替身或者占位符,以控制对该对象的访问。 代理模式在GOF95中分类为结构型模式。
- Proxy和RealSubject具有共同的接口Subject,以满足里氏替换原则
- Proxy一般需要持有RealSubject的引用,以便在必要是将请求转发给真实对象
- RealSubject通常是真正做事的对象,Proxy会访问Client对RealSubject的访问
所谓“替身/占位符”,就是它代表某个真实的对象,一个常见的例子的是远程代理,即远程对象的本地代表。所谓远程对象是指不是运行在同一个地址空间中的对象,不能通过直接的函数调用来访问。为了向Client屏蔽远程对象,在客户端所在地址空间,创建一个Proxy,提供与真实对象一致的接口即可,具体的进程间或者网络通信由代理对象负责。
代理可以提供多种控制访问功能,从而形成不同类型的代理:
- 远程代理控制访问远程对象
- 虚拟代理控制访问创建开销大的资源,并将RealSubject的创建尽量的推迟。虚拟代理一个变体是写入时复制(Copy-On-Write)代理,避免不必要的数据复制,Linux内核对也使用了写入时复制这一思想来增强fork()的性能
- 保护代理基于权限控制对资源的访问
- 缓存代理通过缓存来提升性能。在Web服务器中使用的很多
- 智能引用代理:在访问一个对象的时候可以执行一些Housekeeping操作,例如引用计数
- ……
RMI提供了客户辅助对象——存根(Stub),以及服务辅助对象——骨架(Steleton),用来为Client创建与服务对象相同的方法。新版的Java已经不需要在服务器端显示提供Steleton类。使用RMI时不需要编写任何网络和I/O代码,对于Client,就好像在调用本地JVM上的方法一样。下面是一个简单的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
//JDK1.2以后不需要产生skeleton,RMI直接使用反射把客户端调用分发给远程服务 //JDK1.5以后,连stub都不需要了,结合RMI和动态代理机制,远程对象的Stub成为java.lang.reflect.Proxy的实例。rmic命令已经退出历史舞台 //客户端和服务器公共代码 import java.rmi.Remote; import java.rmi.RemoteException; //支持远程方法调用的服务端必须继承RMI提供的接口 public interface RemoteEchoService extends Remote { String echo( String request ) throws RemoteException;//接口方法必须声明跑出RMI规定的异常 } //服务器代码 import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; //远程服务类至少实现一个Remote接口,并且为远程对象定义一个构造器 //继承UnicastRemoteObject类,自动导出为远程对象 //只有导出的对象才能接受RMI调用 public class RemoteEchoServiceImpl extends UnicastRemoteObject implements RemoteEchoService { private static final long serialVersionUID = 1L; //由于对象导出本身就会跑出RemoteException,因此构造器需要声明抛出 protected RemoteEchoServiceImpl() throws RemoteException { super(); } public String echo( String request ) throws RemoteException { return request; } } public class ServerMain { public static void main( String[] args ) { try { //创建RMI注册表,监听默认的1099端口 LocateRegistry.createRegistry(1099); //初始化服务类 RemoteEchoService service = new RemoteEchoServiceImpl(); //注册到RMI注册表(rmiregistry),客户端可以通过//host/objectname形式查找到此远程对象 Naming.rebind( "remoteEchoService", service ); //服务器端自动以守护模式运行,等待远程调用请求到达 } catch ( Exception e ) { e.printStackTrace(); } } } //客户端代码 public class ClientMain { public static void main( String[] args ) { try { String url = "//localhost/remoteEchoService"; RemoteEchoService service = (RemoteEchoService) Naming.lookup( url ); service.echo( "Hello" ); //service是动态代理对象 } catch ( Exception e ) { e.printStackTrace(); } } } |
java.lang.reflect包中内置了代理的支持,利用该包可以在运行时动态创建一个代理类,实现一个或者多个接口。在此包中,Proxy角色已经被创建好了,我们需要提供InvocationHandler的实现来控制对真实对象的访问,Proxy上的任何方法调用都会转发到InvocationHandler。下面是一个基于JDK动态代理机制的保护代理的例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
public interface Person { public String getName(); public String getHobby(); public int getAge(); public String getTel(); } class PersonImpl implements Person { ... } public class BrowserInvocationHandler implements InvocationHandler { private PersonImpl person; public BrowserInvocationHandler( PersonImpl p ) { person = p; } public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable { try { //禁止浏览时获取电话 if ( method.getName().equals( "getTel" ) ) { throw new IllegalAccessException(); } else { return method.invoke( person, args ); } } catch ( InvocationTargetException e ) { return null; } } } //这里是一个交友网站,用户在浏览时,只能访问到会员的基本信息 public class DatingProtectionProxy { public PersonImpl createProxy( PersonImpl p ) { ClassLoader cl = p.getClass().getClassLoader(); Class<?>[] interfaces = p.getClass().getInterfaces(); //通过静态工厂创建代理的实例 return (PersonImpl) Proxy.newProxyInstance( cl, interfaces, new BrowserInvocationHandler( p ) ); } } |
Java开源街的字节码生成器允许以编程的方式,在运行时动态的决定类的结构, 比起动态代理,它允许Proxy继承RealSubject,更加灵活易用(JDK动态代理强制要求Proxy必须实现某些接口)。
这些字节码生成器中比较著名的有cglib、Javassist,ORM框架Hibernate就使用过这两个框架,来实现持久化对象的虚拟代理:
LazyInitializer类层次中包含了虚拟代理的大部分逻辑——仅仅在必须时才到持久化存储中加载实体对象。
- 与装饰模式的区别:意图不同,装饰者是为对象添加行为;代理则是控制对象的访问。此外代理可能会负责(延迟的)创建被包裹的对象;装饰器则从来不创建被包裹的对象
- 与工厂模式的联用:可以通过工厂来创建Proxy
Leave a Reply