代理这个词并不陌生,是一个比较常用的设计模式,可以叫他委托模式,也可以叫他代理模式。我也比较常用这个模式,但是在使用的时候往往会出现一些不是很让人舒服的地方(下文会具体说明),因此我常常会怀疑自己是不是不该用这个模式,最终想不出个所以然来,导致有部分代码不知道应该怎么美化。
本文目的仅仅在于 认知 & 学会使用 动态代理,具体的深究与代理的生成只会稍微一笔带过,不打算详细说明,有兴趣可以自己去查阅资料深究一下,我这三脚猫功夫就不误人子弟了。
代理模式
首先稍微复习一下代理模式,也就是和动态代理模式相对的静态代理模式,将他的缺点暴露出来,也就能更加理解动态代理模式的用处了。
1 | //动作接口 |
上面是一个 代理接口 实际对象 代理对象 的代理模式,代理模式的优点: 扩展原功能,不侵入原代码。然而在使用的时候,并非只会有一个需要代理的动作,比如有doSth,就会有doSthTwo,doSthThree等;另外每一个Action的真实实现者并非只有一个,有RealObj就会有RealObj2、RealObj3。那么想一下,在这样的情况下静态代理会变成怎么样呢???
首先,不使用代理是这样的:
1 | new RealObj().doSth(); |
接着,方案一:为每一个Action分别用上静态代理,就会像这样:
1 | proxy1.doSth(); |
问题是什么呢?你需要给每一个动作写一个专门的ActionProxy类,每新增一个动作,多一个代理类,久而久之类就爆炸了..不方便与维护。对于这个问题,虽然有一个解决方案二:只需要一个ActionProxy类,但是实现多个Action接口,这样的缺点就是需要不停的修改这个类的代码;或者是将Action接口中规定多个需要实现的方法,缺点同上一个,需要不停扩展interface以及proxy类。
毫无疑问,仅仅为了扩展同样的功能,在方案一中,我们会重复创建多个逻辑相同,仅仅RealObject引用不同的Proxy。而在方案二中,会导致proxy的膨胀,而且这种膨胀往往是无意义的。此外,假如方法签名是相同的,更需要在调用的时候引入额外的判断逻辑。这也就是我经常会遇到的一个让我不爽的事情了。。。
动态代理模式
上面花了挺多篇幅来说明静态代理的问题,这是很有必要的,因为只有知道了静态代理会有什么缺点,才能更深刻了解,动态代理在上什么时候适合使用。
动态代理类需要实现一个关键的接口:InvocationHandler
1 | //这个是java对InvocationHandler接口的定义,我们发现需要实现invoke方法 |
DynamicProxyHandler 写完了,可以看到在handler中大量用到了object,使用超类是为了能够让他处理所有类型的对象。接下来的问题是怎么用呢?怎么让java自动帮我们生成所需的代理对象呢?
1 | public static Object newProxyInstance(ClassLoader loader, |
使用Proxy.newProxyInstance的方法来获取一个object对象,这个object对象就是我们所需要的proxy代理者。再来看看这个newProxyInstance方法所需要的参数,看起来很复杂的样子。。
第一个参数 ClassLoader loader 需要一个类加载器,这个要扩展开就很复杂了,我也说不清,请自行查询。
第二个参数 Class<?>[] interfaces ,需要的是实现的接口,比如一开始的例子中,实现了Action接口,那么在这里就传入Action 接口 new Class[]{Action.class}(由于可能实现多个接口,那就使用一个class数组来传递)
第三个参数 InvocationHandler h,一个实现了InvocationHandler接口的对象,那一想就能猜到,就是我们上面写的DynamicProxyHandler类了
最后完整的看一下使用方法:
1 | RealObject realObject = new RealObject(); |
对比静态代理中的方案一,生成一个包含我们扩展功能,持有RealObject引用,实现Action接口的代理实例Proxy。只不过这个Proxy不是我们自己写的,而是java帮我们生成的。
让我们再回顾一下代理三要素:实际对象:RealObject,代理接口:Action,代理对象:Proxy
上面的代码实含义也就是,输入 RealObject、Action,返回一个Proxy。妥妥的代理模式。
综上,动态生成+代理模式,也就是动态代理。
附上个人觉得比较好的模式
1 | //这个Handler中的invoke方法中实现了代理类要扩展的公共功能。 |
可以看到,我取消了构造方法,封装了一个getProxy方法来获得proxy对象,只要传入一个realObject对象即可获得代理对象。就像这样:
1 | Action proxy = (Action) new DynamicProxyHandler().getProxy(new RealObj()); |
好了,到这里,大概讲了一下动态代理是什么,能解决什么问题,简单的使用方法。也止步于这里了,深入的研究比如newProxyInstance 究竟干了什么 ,是如何生成对象的,这个就自己研究吧,我就不丢人了。