java 为什么使用动态代理?

什么是静态代理、动态代理?动态代理有什么优点?

参考:https://www.zhihu.com/question/20794107

 

既然存在动态代理,那么相对的,肯定有静态代理。

要明白为什么使用动态代理,首先要明白静态代理存在什么问题。

一、静态代理

(1)实现被代理类

现在有以下需求:需要实现一个字体提供类,从磁盘、网络、系统获取字体。

首先实现一个字体提供接口,有一个方法用于提供字体:

FontProvider.java

写一个实现类FontProviderFromDisk,从磁盘获取字体。再写一个工厂类ProviderFactory,用于提供实现类。

ProviderFactory.java

现在我们可以通过工厂类获取实现类,调用实现类中的方法:

Main.java

(2)实现静态代理

现在又有一个新的需求:希望给被FontProviderFromDisk类加上缓存功能。

如何实现?有两种方案。

1.直接修改FontProviderFromDisk类

我们的第一反应就是:直接修改FontProviderFromDisk类,加上缓存功能。

改写成这样:

FontProviderFromDisk.java

好像已经实现了缓存功能啦。

但是别高兴得太早,我们需要从磁盘、网络、系统获取字体。也就是说,共有三个实现类:FontProviderFromDisk、FontProviderFromNet、FontProviderFromSystem。

如果我们要让所有实现类都实现缓存,怎么办?在每个实现类中都写上缓存处理吗?

目前我们只是实现缓存功能而已。万一以后想要加上更多功能,例如参数校验、日志处理、权限检查、异常处理,怎么办?我们依然在每个实现类中重复实现吗?这种做法显然非常麻烦。

还有一种情况,就是这个类已经被别的类聚合/依赖了(别人已经在这个类的基础上进行了一系列的开发)。如果我们再去修改原类,可能导致功能重复/引起不必要的错误。

2.实现一个静态代理类(装饰器模式)

既然原类不能改,那么我们另外实现一个静态代理类CacheFontProvider(装饰器类)

ProviderFactory.java

更改一下调用的方式,就可以实现缓存功能:

Main.java

无论是FontProviderFromDisk、FontProviderFromNet,还是FontProviderFromSystem,只要通过静态代理类进行包装,并且修改main方法中注入的FontProvider实现类,就能实现不同的缓存功能。

那么问题来了:这里的静态代理实现方式,怎么这么像装饰器模式?(其实这就是装饰器模式)

个人理解:装饰器模式的思想,是在拓展方法的功能时,不对原本实现类进行改动,而是另写一个装饰器类对实现类进行装饰(实现更多方法),从而拓展原实现类的功能。

这就对了,装饰器模式本身就可以理解为静态代理。(确实可以这样理解,实现的效果相近,实现的方式也相似。虽然出发点有些许不同,但是无伤大雅)

顺带一提,我们经常在不知不觉中使用装饰器模式。

举个例子,在spring mvc项目中(存在mapper,dao,service结构),我们一般不会直接使用dao中的方法,需要在service中进行重新包装(进行参数的校验、错误的捕获),再去调用service中的代理方法。这是装饰器模式的一种运用。

(2017.11.2 这里似乎写得有些问题?我写的好像是装饰器模式,不是动态代理…)

根据http://www.cnblogs.com/doucheyard/p/5737366.html的说法:

装饰器模式主要是强调对类中代码的拓展,而代理模式则偏向于委托类的访问限制。但是,两者都一样拥有抽象角色(接口)、真实角色(委托类)、代理类 。

3.更正一下,静态代理应该写成这样:

ProviderFactory.java

Main.java也要随之改变:

个人感觉,仅有的区别是:FontProvider这个被包装类是否需要写死。

现在我们回想一下,使用这两种方式的最终目的是什么?不就是为了拓展原类的功能吗(起一个包装作用)?在这一层面上,就算把装饰器模式理解为静态代理无伤大雅。

在我的参考链接中,有作者表示:

装饰模式本身就可以理解为静态代理。

现在我比较认同这个观点。

二、动态代理

(1)为什么使用动态代理?

既然静态代理用得好好的,我们为什么要使用动态代理呢?

假设现在来了新需求:存在多个提供类(不止需要提供字体,还需要提供图像、提供音乐…..),每个类都有getXxx(String name)方法。如果每个类都要加入缓存功能,我们怎么办?

此时,如果使用静态代理、装饰器模式,实现过程会非常麻烦。我们需要(为每个方法)手动创建代理类,再创建相应的获取方法,最后修改相应的调用。

那么问题来了,如果共有1000个方法,我们不得不把以上步骤重复1000次…难道没有更加优雅的方法吗?

这时候就轮到动态代理出场了(没有别的选择了)。

(2)使用动态代理解决问题

假如我要给一个图片提供类加上缓存功能,这样做就可以了:

首先写好图片提供类的接口(无接口无法实现原生的动态代理)。

ImageProvider.java

之后写一个实现类,假设从硬盘获取图片(这个类在ProviderFactory.java中)。

之后我们实现代理类CachedProviderHandler,并且写出获取代理对象的方法getFontProvider和getImageProvider。

ProviderFactory.java

最后在main方法中分别使用两个代理对象:

Main.java

运行结果为:

可以发现,如果使用动态代理是通用的,复用率极高。相比之下,静态代理就显得过于繁琐了。

三、总结

总之,代理模式常用于拓展代码的情景,使用静态代理和动态代理均可。需要考虑具体实现难度,选用哪一种实施。

发表评论

电子邮件地址不会被公开。 必填项已用*标注