代理模式(Proxy)
思想概要
代理模式的基本思想来源于“对修改封闭”这个原则,对于某些不是你亲自编写的代码,你最好不要去修改它来扩展其功能,而是通过包装一个代理类去扩展它。因为对方代码可能会在新的版本中被改变,而你对之前版本做的代码层次的修改很有可能在第三方类被替换的时候失去功能。但是请从应用上区分代理模式和装饰模式,这两个模式在结构上惊人的相似,但是当你说出这两个模式的名字时,大家默认的认为,代理模式是对被代理的真实类的功能限制,而装饰模式是对被装饰类的功能加强。这种区分并不是代码结构本身带来的,而是约定俗成的认知。这就意味着代理模式通常会在调用真实类API前做一些条件判断和环境设置,而装饰模式的装饰者类自身就有某些状态数据和功能,在客户调用API时会得到某种加强的功能。
包装一个代理类,通过调用被代理的类的API来扩展其功能这种方法有一个明显的好处:第三方开发者们无论他们什么时候发行新版本,他们都必须保证公开过的API在功能上和入口上的一致性。所以无论什么时候,调用公开API来扩展功能都是安全的。当然,还有一个很重要的因素就是你未必有能力去修改第三方的代码,大多数时候它们都是以.class的形式发布的。

盗用一张常见的代理模式的类图,图中清晰的展示了真实类和代理类的关系。代理模式为了让最终用户(代码层次的用户)并不去关心自己使用的是代理类还是真实类,它也实现了真实类所实现的接口,换句话说,代理类拥有真实类的一切API特征。但是,图中的虚箭头提醒了我们:代理类包含真实类,它是调用真实类的API来实现的。当然,代理类并不是单纯的转义真实类,否则直接使用真实类就好了,它会在调用真实类的API时追加某些扩展功能,比如在调用真实类API的前后输出Log,或者在调用之前准备好运行环境,在调用结束后删除中间文件等。
讨论代理模式的时候,常常都把重点放在有三类实现方法上,而我这里就简单描述下动态代理,我感觉用的比较多的就是这个了。至于spring常用的cglib方法,实在惭愧,本人并没有研究过,留作今后努力的吧。
动态代理
动态代理区别静态代理的地方就是利用反射来减少代码量,对于程序员来说既简洁又健壮,毕竟代码这东西写得多错得多,使用JDK层级的代码来帮助我们减少代码是很明智的。至于减少了哪些代码呢?如果您亲自写一遍静态代理就知道,代理类需要一一实现Subject接口中的所有API,大多数情况下这些实现都很简单,仅仅是调用真实类中的对应函数而已,这样的实现即无趣又容易错。
针对动态代理,JSK提供了一个重要的函数:
Object Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h)
这个函数能够以反射机制为真实类生成一个代理类,它的第一个参数就是真实类的ClassLoader,第二个参数是这个真实类所实现的接口,如果真实类没有实现接口,这里的调用将无法进行。第三个参数是个回调接口,newProxyInstance执行结束后得到的Object就是我们需要的代理类,而调用这个代理类的API可以被反射机制定位到真实类的API去,这个回调接口就是定位器。
public class SubjectInvocation implements InvocationHandler { private final Object refObject; public SubjectInvocation(Object object){ refObject = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { /* Call */ System.out.println(method.getName() + "-" + "Before"); Object res = method.invoke(refObject, args); System.out.println(method.getName() + "-" +"After"); /* Return */ return res; } }
定位器的代码很简单,它的核心有两点:
1)保存真实类的句柄,这算是定位的对象。
2)实现InvocationHandler接口,这是回调专用的接口。
有这两个特征后,接下来的代码就简单了,就是在invoke里做一个真实类和Method的转接。
method.invoke(refObject, args)
核心部分的介绍就是这些,接下来把代码写全,整体把握一下代理模式:
public interface ISubject { /* API */ void doSomething(); }
接口定义
public class RealSubject implements ISubject { @Override public void doSomething() { System.out.println("Do Something"); } }
主函数部分,这部分代码将真实类的构造方法,接口信息和定位器指定给newProxyInstance函数,让JDK帮助创建代理类。
public class Launcher { public static void main(String[] args) { ISubject real = new RealSubject(); ISubject proxy = (ISubject)Proxy.newProxyInstance(real.getClass().getClassLoader(), (Class<?>[]) real.getClass().getGenericInterfaces(), new SubjectInvocation(real)); if (proxy != null) { proxy.doSomething(); } } }
运行结果
doSomething-Before
Do Something
doSomething-After
好了,现在我们不要被这么多代码给迷惑了我们本来的目标:扩展真实类的功能。通过代码我们其实可以发现,接口定义和真实类是第三方库所实现的,而主函数部分是JDK的标准调用方式。扩展真实类的部分其实仅仅在定位器中,这是我们唯一需要花精力去设计的地方。实现方可以通过对Method,args等参数的操作来扩展第三方库的功能。
请留意本文描述的代理模式是基于某个接口的,虽然我们反复提倡基于接口进行编程,但是厌恶代码文件数量过多的朋友仍然不在少数,或者说某些类当初设计的时候并没有想到它会在某个时候被第三方来做扩展,这样的结果就是大量的类不具备自己的接口,使用代理模式来扩展这些类需要特殊的技巧,这就是文章开头介绍的cglib的主要工作,不在本文的scope内。

浙公网安备 33010602011771号