设计模式-代理模式
一、概念
代理模式是对象的结构模式,代理模式给某一个对象提供一个代理对象,并由这个代理对象控制对原对象的访问。
二、模式动机
比如有些对象的访问是有权限的,在访问这个对象之前需要进行权限检查,那么就给这个对象提供一个代理对象,这个代理对象对客户而言,是完全透明的,客户在使用这个代理对象时,和使用原对象一模一样,没有任何区别,即客户跟本感觉不到使用的是一个原对象的代理对象。这个代理对象就起到了客户和原对象之间提供了一个间接性,在这个间接性里面,就可以进行权限判断,判断客户是否有权限调用原对象。
还有一种虚代理的情况,如一个对像非常消耗我们的资源,如时间资源或者内存资源,但客户只是在某些情况下需要看到这个对象的所有的完整信息,而这时就可以给这个对象提供一个代理对象,该代理对象只包括原对象的部分信息,当客户需要的信息超出当前已包括的部分信息时,才对不存在的那部分信息进行加载。该对象对客户而言,和原对象没有任何区别,但是某一时刻这个代理对象可能只包括原对象的部分信息,所以它不完全等同于原对象,所以叫它虚代理对象。
三、模式的结构
角色分析:
SubjectProxy:代理对象,和被代理对象实现同样的接口,这样就可以使用这个代理对象代替被代理对象。该代理对象保存一个被代理对象的引用,并控制这个被代理对象的访问。
Subject:目标接口,代理对象和被代理对象共同的接口,这样代理对象就可以代替被代理对象。
RealSubject:真实主题,被代理对象。
代码示例:
package proxy; /** * 目标接口,代理对象和被代理对象共同的接口,这样代理对象就可以代替被代理对象。 * @ClassName: Subject * @author beteman6988 * @date 2018年2月3日 上午9:07:01 * */ public interface Subject { public void request(); } package proxy; /** * 被代理对象,实现具体的业务逻辑 * @ClassName: RealSubject * @author beteman6988 * @date 2018年2月3日 上午9:07:54 * */ public class RealSubject implements Subject { /** * 具体的业务逻辑 * @Title: request * @param * @return void * @throws */ @Override public void request() { //具体的业务逻辑 } } package proxy; /** * 代理对象类,里面引用真实的被代理对象 * * @ClassName: SubjectProxy * @author beteman6988 * @date 2018年2月3日 上午9:10:49 * */ public class SubjectProxy implements Subject { // 被代理对象 private RealSubject realSubject = null; public SubjectProxy(RealSubject realSubject) { this.realSubject = realSubject; } @Override public void request() { // 控制判断是转调被代理对象 realSubject.request(); } } package proxy; public class Client { public static void main(String[] args) { //被代理对象 RealSubject realSubject=new RealSubject(); //代理对象:代理了realSubject Subject subject=new SubjectProxy(realSubject); subject.request(); } }
四、模式样例
静态代理:以上示例代码的模式属于静态代理模式,所谓静态代理模式,就是代理类是自已实现的,也就是代理类必须由程序员事先定义好。这种模式有一个比较不好的地方,就是当subject接口发生变化时,代理类也要跟着发生变化,不够灵活。
动态代理:所谓动态代理,就是代理类是自已动态生成的,程序员不用事先定义代理类,也就是给定一个subject接口,就可以自动产生实现这个subject接口的代理类,这样如果接口发生了变化,代理类会跟据变化后的接口动态产生新的代理类。目前JAVA中是支持动态代理的,实现方式为通过JAVA的反射机制进行实现,下面将重点分析JAVA中动态代理的实现原理。
JAVA实现动态代理所需要支持的类如下:
1.java.lang.reflect.InvocationHandler:InvocationHandler
是代理实例的调用处理程序 实现的接口。每个代理实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke
方法。
这个就是JDK的官方解释,大概意思就是,每个代理对象都要有一个关联的调用处理程序,当对这个代理对象调用方法时,代理对象就会将调用请求自动转调到调用处理程序的invoke方法。这个接口就是调用处理程序必须实现的接口。这个接口的结构如下:
它只提供了一个唯一的接口方法invoke: Object invoke(Object proxy, Method method, Object[] args) throws Throwable
我们稍后会对这个接口方法中的三个参数进行分析。
2.java.lang.reflect.Proxy:Proxy
提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
这个就是JDK的官方解释,大概意思就是,Proxy类可以提供创建动态代理类的静态方法,也提供了创建代理对象的静态方法。且通过它创建的动态代理类的超类就是这个类。
这个类的结构如下:
从上面的结构图可以看到,它对外提供了4个静态的public方法,如下:
1.public static Class<?> getProxyClass(ClassLoader loader,Class<?>... interfaces)
该方法中法的第1个参数表示类加载器,第二个参数就是subject接口集合,即通过提供接口集合产生实现subject的动态代理类,相当于代理模式中的SubjectProxy角色。
2.public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) throws IllegalArgumentException
通过该方法可以产生动态代理类的一个代理对象,第1个参数表示类加载器,第二个就是subject接口集合,第三个参数表示该代理对象关联的调用处理程序。
3.public static boolean isProxyClass(Class<?> cl)
判断一个类是否是为动态代理类
4.public static InvocationHandler getInvocationHandler(Object proxy)throws IllegalArgumentException
返回一个动态代理对象的调用处理程序
getProxyClass和newProxyInstance是我们要重点分析的方法:
getProxyClass:产生一个动态代理类,首先他是一个类。我们经常看到的类都是有一个对应的.java文件,但是这个类是jdk动态产生的,我们并不能查看他产生的.java原代码是什么样,虽然看不到它具体产生的代码是什么样,但我们也可以进行一定的推测。首先这个代理类肯定implents 该方法中第二个参数提供的所有接口。
newProxyInstance:产生一个动态代理类的一个实例,即产生一个代理对象,通过查看JDK的源代码可知,他首先通过调用getProxyClass获得产生的动态代理类,然后获得该动态代理类的构造函数,通过构造函数实例化一个动态代理对象,原代码如下:
可以看到,该方法返回了一个动态代理对象,问题来了,动态代理对象中的被代理对象在哪?没看到!有没有提供set方法?没有,那么实例化该代理对象时可否通过构造函数的参数传进去,答案是否定的。通过cons.newInstance(new Object[]{h})可以看出,为构造函数提供的是一个调用处理程序(实现了InvocationHandler接口的实例)的实例,而并非被代理对象。那么被代理对象怎么置到代理对象里面呢?大胆推测只能包含在InvocationHandler的实例里面。当对代理对象调用方法时,会首先转调到在InvocationHandler的实例的invoke方法,在inovke方法里面再将请求转调到InvocationHandler实例里面所包含的被代理对象,这样就合理了。为了证明我们推测的合理性,可以写一个模拟程序,代码如下:
package proxy.sample; /** * 人类接口 * 被代理对象类和动态代理类都要实现的接口 * @ClassName: IMan * @author beteman6988 * @date 2018年2月3日 下午10:07:09 * */ public interface IMan { /** * 睡觉 * @Title: sleep * @param * @return void * @throws */ public void sleep(); /** * 走路 * @Title: go * @param @return * @return IMan * @throws */ public IMan go(); } package proxy.sample; /** * 超人 * 被代理对象类 * @ClassName: SuperMan * @author beteman6988 * @date 2018年2月3日 下午10:09:16 * */ public class SuperMan implements IMan { @Override public void sleep() { System.out.println("超人在睡觉!"); } @Override public IMan go() { System.out.println("我是超人,我在飞翔!"); return null; } } package proxy.sample; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 调用处理程序 * 将从代理对象委派过来的请求,通过调用处理程序,继续委派给被代理对象 * @ClassName: ManInvocationHandler * @author beteman6988 * @date 2018年2月4日 上午9:33:42 * */ public class ManInvocationHandler implements InvocationHandler { //被代理对象 private Object realObj=null; public ManInvocationHandler(Object realObj) { this.realObj = realObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //将请求委派给被代理对象,并将被代理对象的执行结果返回 System.out.println("调用代理对象的"+method.getName()+"方法"); Object rnt= method.invoke(realObj, args); System.out.println("调用代理对象的"+method.getName()+"方法结束"); return rnt; } } package proxy.sample; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 模拟产生的动态代理类,该动态代理类实现了IMan接口,且它的超类是Proxy类 * * @ClassName: MyProxy * @author beteman6988 * @date 2018年2月3日 下午10:12:04 * */ public class MyProxy extends Proxy implements IMan { /** * 该类的构造函数 * 为什么它的参数是InvocationHandler,通过Proxy.newProxyInstance方法中的 * cons.newInstance(new Object[]{h})可以看出,该类的构造函数参数必定是InvocationHandler类型 * * @param h */ protected MyProxy(InvocationHandler h) { /** * 调用父类的构造函数 * 先实例化父类,并将调用处理程序置入到父类的protected InvocationHandler h成员 * 从父类的protected InvocationHandler h是protected,所以proxy的子类,即当前类 * 可以直接访问这个受保护的成员。 */ super(h); } @Override public void sleep() { Object obj=null; Method method=null; try { Class<IMan>[] interfaces=(Class<IMan>[]) this.getClass().getInterfaces(); for(Class aInter:interfaces) { method=aInter.getMethod("sleep", null); } obj=super.h.invoke(this, method, null); } catch (Throwable e) { e.printStackTrace(); } } /** * 实现IMan接口的方法 * 该接口很重要的一个职责就是要转调调用处理程序,由调用处理程序通过invoke将请收继续转调到被代理对象,并返回被代理对象执行过后返回的结果。 */ @Override public IMan go() { Object obj=null; Method method=null; try { /** * 获取动态类实现的所有接口,并对每个接口进行编历,此处实现颇为简单,考虑的很少,只是模拟代码,真实的实现可能比较复杂 */ Class<IMan>[] interfaces=(Class<IMan>[]) this.getClass().getInterfaces(); for(Class aInter:interfaces) { //获取当前方法在接口中的方法对象,并将方法对象传给调用处理程序 method=aInter.getMethod("go", null); } /** * 划重点,将请求转调给调用处理程序的invoke方法 * 第一个参数为当前代理对象,第二个参数为接口方法对象,第三个为接口方法对象的参数对象,暂且认为为null */ obj=super.h.invoke(this, method, null); } catch (Throwable e) { e.printStackTrace(); } return (IMan)obj; } } package proxy.sample; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { //实例化被代理对象 IMan aMan=new SuperMan(); //将被代理对象置入到调用处理程序 InvocationHandler handler=new ManInvocationHandler(aMan); //实例化代理对象,并将调用处理程序置入到代理对象 IMan man=(IMan)new MyProxy(handler); //IMan man=(IMan)Proxy.newProxyInstance(SuperMan.class.getClassLoader(),SuperMan.class.getInterfaces() , handler); //请求代理对象的方法 man.go(); man.sleep(); } }
执行结果如下:
调用代理对象的go方法
我是超人,我在飞翔!
调用代理对象的go方法结束
调用代理对象的sleep方法
超人在睡觉!
调用代理对象的sleep方法结束
如果将Test类的Main方法进行修改,使用JAVA提供的动态代理会是什么样的情况呢?
package proxy.sample; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { //实例化被代理对象 IMan aMan=new SuperMan(); //将被代理对象置入到调用处理程序 InvocationHandler handler=new ManInvocationHandler(aMan); //实例化代理对象,并将调用处理程序置入到代理对象 //IMan man=(IMan)new MyProxy(handler); //使用JAVA提供的动态代理 IMan man=(IMan)Proxy.newProxyInstance(SuperMan.class.getClassLoader(),SuperMan.class.getInterfaces() , handler); //请求代理对象的方法 man.go(); man.sleep(); } }
执行结果如下:
调用代理对象的go方法
我是超人,我在飞翔!
调用代理对象的go方法结束
调用代理对象的sleep方法
超人在睡觉!
调用代理对象的sleep方法结束
从执行结果看,我们完全模拟实现了JAVA的动态代理,即我们模拟出了代理类。并了解了代理对象如何转调调用处理程序,并通过调用处理程序将请求进一步委派给被代理对象去执行。
时序图如下:
调用处理程序中的invoke(Object proxy, Method method, Object[] args) 第1个参数proxy,这是个什么东东?貌似代理对象?被代理对象?
假设它是代理对象,如上例,如果它是代理对象,那么如果对这个对象继续转调go()方法,会出现什么问题?会出现死循环,为什么会出现死循环呢,程序的运行路径是这样的:代理对象go()方法---》转调调用理理程序的invoke, invoke方法里面再调用代理对象的go()方法,那么就会出现一个闭合的死循环,可以用下面的代码去验证:
package proxy.sample; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; /** * 调用处理程序 * 将从代理对象委派过来的请求,通过调用处理程序,继续委派给被代理对象 * @ClassName: ManInvocationHandler * @author beteman6988 * @date 2018年2月4日 上午9:33:42 * */ public class ManInvocationHandler implements InvocationHandler { //被代理对象 private Object realObj=null; public ManInvocationHandler(Object realObj) { this.realObj = realObj; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //将请求委派给被代理对象,并将被代理对象的执行结果返回 System.out.println("调用代理对象的"+method.getName()+"方法"); //Object rnt= method.invoke(realObj, args); Object rnt= method.invoke(proxy, args); //将请求委派给代理对象,将会出现死循环 System.out.println("调用代理对象的"+method.getName()+"方法结束"); return rnt; } }
运行结果如下:
一直出现死循环,直到将内存耗尽,无论是用自已写的类或者JDK自已提*生的动态类,结果都是一样,证明我们的推断是正确的。第1个参数就是代理对象,而非被代理对象,这也就解释了,被代理对象需要被包含在调用处理程序中,且在invoke方法中,将请求必须转调到被代理对象。
再进一步推测,如接口的go()方法返回的是当前对象,如果在invoke将传入的proxy对象返回,那么对这个返回的对象可否直接调用sleep对象呢,如man.go().sleep(); 答案是肯定的(这种测试仅限上面的用例 ,正常情况下不会这么做)。
(前提是代理对象的go()方法返回了invoke的结果,且将结果转换成了IMan类型,否则man.go().sleep()是不成立的。)
运行结果如下:
调用代理对象的go方法
我是超人,我在飞翔!
调用代理对象的go方法结束
调用代理对象的sleep方法
超人在睡觉!
调用代理对象的sleep方法结束
代理模式在现实生活就还是比较多的,如红娘就是一个典型的代理对象,不过他是双向代理,在与男方沟通时他代表了女方,在与女方沟通时他又代表了男方。还有如代理律师等,在此不一一枚举。
五、与其它模式的关系
代理模式和对象适配器模式:这两个模式有相似性,他们都为另一个对象提供间接性的访问,二者都是从自身以外的接口向这个对象转发请求。但从功能上来讲,两个模式是不一样的。适配器模式主要是解决接口之间不匹配的问题,它通常是为所适配的对象提供一个不同的接口,而代理模式会实现和目标对象相同的接口。
代理模和装饰模式:这两个模式从实现上相似,但是功能上是不同的。装饰模式的实现和保护代理模式的实现上类似,二者都是在转调其它对象前后执行一定的功能,但是目的不同,装饰模式的主要目的是动态的增加工能,而代理模式的主要目的是控制访问。