代理模式(Proxy),为其他对象提供一种代理以控制对这个对象的访问。
在面向对象编程中,直接引用某些对象会因为种种原因(比如对象创建的开销过大,访问需要安全控制,或者需要跳出当前进程等)带来很多问题,给用户或者系统本身带来不便或者异常。这时,就需要在操作对象(客户端)和被调用对象(真实对象)之间添加一个代理,以协助操作对象控制对被调用对象的访问,起到透明中介的作用。
代理模式的应用:一是远程代理,即为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实;二是虚拟代理,即根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象;三是安全代理,用来控制真实对象访问时的权限;四是智能指引,即当调用真实对象时,代理处理另外一些事。
代理模式的实现,主要有以下几个特点:
1.真实对象和代理对象拥有共同的父类或者实现相同的接口,实现了相同的方法;
2.代理对象引用或者包含真实对象的一个实例,该实例在代理对象中初始化,代理对象通过1中所述的相同方法来替代对真实对象的调用;
3.客户端中不再实例化真实对象,完全通过代理对象来访问真实对象。
代理模式的简单示例,参见如下地址:
http://www.cnblogs.com/kid-li/archive/2006/10/18/532192.html
顺带一提的是,百度百科“java代理模式”词条中的代码示例实际上是装饰模式,而不是代理模式。
在Java中,通常使用Proxy(动态代理)来实现对代理模式的支持。动态代理的详细描述参见《Java核心技术(卷1)》6.5节·代理。
Java中要创建一个代理对象,必须调用Proxy类的静态方法newProxyInstance,该方法的原型如下:
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler) throws IllegalArgumentException
其中:
loader,表示类加载器,对于不同来源(系统库或网络等)的类需要不同的类加载器来加载,这是Java安全模型的一部分。可以使用null来使用默认的加载器;
interfaces,表示接口或对象的数组,它就是前述代理对象和真实对象都必须共有的父类或者接口;
handler,表示调用处理器,它必须是实现了InvocationHandler接口的对象,其作用是定义代理对象中需要执行的具体操作。
InvocationHandler之于Proxy,就如Runnable之于Thread。InvocationHandler接口中只有一个方法invoke,它的作用就跟Runnable中的run方法类似,定义了代理对象在执行真实对象的方法时所希望执行的动作。其原型如下:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
其中:
proxy,表示执行这个方法的代理对象;
method,表示真实对象实际需要执行的方法(关于Method类参见Java的反射机制);
args,表示真实对象实际执行方法时所需的参数。
在实际的编程中,需要优先定义一个实现InvocationHandler接口的调用处理器对象,然后将它作为创建代理类实例的参数。(抑或在调用newProxyInstance方法时使用匿名内部类。)这样就得到了代理对象。
真实对象本身的实例化在调用处理器对象内部完成,实例化时需要的参数也应该及时传入调用处理器对象中。这样一来就完成了代理对象对真实对象的包装,而代理对象需要执行的额外操作也在invoke方法中处理。
其后,在客户端中,如果需要使用真实对象时,就可以用代理对象来替代它了(有时需要类型强制转化)。
下面的示例用Java的动态代理模拟了一个拦截器的功能:对于实现了Action接口的类,如果类名不以Action结尾,则不予执行。
共同接口Action: 实现了Action接口的三个真实对象: 调用处理器对象FilterHandler,它是对拦截器的实现: 客户端:最后需要特别说明的是:
1.Proxy创建的代理对象实例包含Object类中的全部方法和接口数组(即newProxyInstance方法中的interfaces参数)中的全部方法。但是,代理类会覆盖Object类中的toString、equals和hashCode方法,而clone、getClass等方法则没有重新定义。另外,所有这些方法都需要在InvocationHandler的invoke方法中通过调用Method对象的invoke方法才能真正被执行。
2.对于特定的类加载器和预设的一组接口来说,即便是调用处理器handler不一样,也只能得到同一个代理类。也就是说,示例中三次对newProxyInstance方法的调用,其实只是得到的同一个对象的三个实例而已,并不是三种不同的类。
其它Java动态代理的细节参见《Java核心技术》或Java API。