动态代理——从一窍不通到恍然大悟
来源:互联网 发布:淘宝家装物流干线运费 编辑:程序博客网 时间:2024/06/11 05:46
前言
动态代理是Java常见的一种设计模式,很多文章都介绍了什么是代理、静态代理和动态代理的实现方式,然而这些都偏理论,一篇好的文章要让大家知道知识点的具体用处,本文在博主看了几篇博文之后的总结和细化,希望能帮助大家的理解。
一个简单的代理
代理就是为其他对象提供一个代理以控制对某个对象的访问。比如火车票代售点就是一个代理,它控制要买火车票的人(其他对象)对火车站售票点(某个对象)的访问,由它向火车票售票点买票。
为实现达到这样的效果,可以如下图实现功能设计:
Client类就是想买火车票的人,Proxy类就是火车票代售点,RealSubject类是火车站售票点,Proxy和RealSubject类提供的买票能力体现在DoAction()方法中,Proxy的DoAction()调用RealSubject的DoAction方法(体现代理的思想),当然Proxy类的DoAction()可以有其他操作,比如欢迎买票人等。
这样一个流程就是体现了简单的代理思想。
稍微实用的代理
换一个复杂点的例子来看,考虑一个字体提供功能,字体库可能源自本地磁盘、网络或者系统。
先考虑从本地磁盘中获取字体,和上面的例子一样,采用代理的方式实现,定义一个提供字体的接口FontProvider:
public interface FontProvider { Font getFont(String name);}
接口的实现类FontProviderFromDisk,也是我们真正提供获取磁盘字体库的类:
class FontProviderFromDisk implements FontProvider{ @Override Font getFont(String name){ System.out.println("磁盘上的字体库"); }}
接着就是实现我们的代理类ProxyForFont:
class ProxyForFont implements FontProvider{ private FontProvider fontProvider; ProxyForFont(FontProvider fontProvider){ this.fontProvider=fontProvider; } @Override Font getFont(String name){ System.out.println("调用代理方法前可以做点事情"); fontProvider.getFont(name); System.out.println("调用代理方法后可以再做点事情"); }}
当我们需要从磁盘获取字体库时,直接调用ProxyForFont就可以了:
public class MyFontProvider{ public static void main(String[] args) { FontProvider fp=new ProxyForFont(new FontProviderFromDisk()); fp.getFont("字体库名"); }}
这样实现的好处在哪儿呢?比如,每次从磁盘获取字体库的时候,磁盘的I/O比较耗时,想通过缓存将读到的字体库暂存一份。此时,我们直接修改ProxyForFont类:
class ProxyForFont implements FontProvider{ private FontProvider fontProvider; ProxyForFont(FontProvider fontProvider){ this.fontProvider=fontProvider; } @Override Font getFont(String name){ System.out.println("检查磁盘缓存中是否存在字体库"); if(不存在){ fontProvider.getFont(name); System.out.println("将磁盘读到的字体库保存到缓存"); }else{ System.out.println("如果存在直接从缓存中获取"); } }}
当然,也可以直接修改FontProviderFromDisk类的getFont方法,但是这样会有一个问题。上文中我们提到,字体库的获取源除了磁盘还有系统和网络等,所以还存在FontProviderFromSystem和FontProviderFromNet两个类,如果这两个类也需要缓存功能的时候,还得再改动这两个类的getFont实现,而代理模式中,只需要在代理类ProxyForFont中修改即可。此时从磁盘、系统和网络获取字体库的方式如下:
public class MyFontProvider{ public static void main(String[]args) { FontProvider fp0=new ProxyForFont(new FontProviderFromDisk()); fp0.getFont("字体库名"); FontProvider fp1=new ProxyForFont(new FontProviderFromSystem()); fp1.getFont("字体库名"); FontProvider fp2=new ProxyForFont(new FontProviderFromNet()); fp2.getFont("字体库名"); }}
可扩展性不言而喻,以上都是静态代理的实现方式,是不是感觉静态代理已经无所不能了呢?我们再来看一个需求。
更实用的代理
以上都是获取字体库,如果想获取图片、音乐等其他资源呢?这个时候一个FontProvider接口就不够用了,还得提供ImageProvider和MusicProvider接口,实现对应的功能类ImageProviderFromDisk,ImageProviderFromSystem,ImageProviderFromNet,MusicProviderFromDisk,MusicProviderFromNet,MusicProviderFromDisk以及代理类ProxyForImage和ProxyForMusic。当要给获取图片和获取音乐等都加上缓存功能的时候,ProxyForImage和ProxyForMusic都需要改动,而缓存的逻辑三个类又是相同的,如此写的代码就不够优美。怎么办,动态代理就发挥作用了:
public class CachedProviderHandler implements InvocationHandler { private Map<String, Object> cached = new HashMap<>(); private Object target; public CachedProviderHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Type[] types = method.getParameterTypes();//获取方法的名、参数等信息 if (method.getName().matches("get.+") && (types.length == 1) && (types[0] == String.class)) {//根据函数的开头名、参数个数、参数类型判断是否是获取资源的方法 String key = (String) args[0];//本文的例子中,接口方法只有一个参数,获取的就是这个传入的参数 //先读缓存 Object value = cached.get(key); //没有就调用从其它媒介获取资源的方法 if (value == null) { value = method.invoke(target, args); //缓存资源 cached.put(key, value); } return value; } return method.invoke(target, args); }}
注意:有了CachedProviderHandler类,原来的ProxyForFont,ProxyForImage和ProxyForMusic类就不需要了。
主函数中依赖CachedProviderHandler和接口功能实现类.+ProviderFrom.+实现代理访问。以从磁盘获取字体集为例子:
public class Main { public static void main(String[] args) { FontProvider realFontProvider = new FontProviderFromDisk(); InvocationHandler handler = new MyInvocationHandler(realFontProvider); FontProvider fontProvider = (FontProvider) Proxy.newProxyInstance(realFontProvider.getClass().getClassLoader(), realFontProvider.getClass().getInterfaces(), handler);//fontProvider就是动态创建的代理类实例 fontProvider.getFont("我的字体集名字", 0); }
main函数中由Proxy.newProxyInstance动态创建的代理对象实例fontProvider调用getFont方法后,JVM提供的动态代理框架会自动调用CachedProviderHandler的invoke方法,invoke方法中可以拿到fontProvider的对象、getFont方法对象以及getFont的String name参数。动态代理中,无需手动实现代理类,传入接口功能实现类(如FontProviderFromDisk)、接口和CachedProviderHandler对象后,Proxy.newProxyInstance动态创建了一个代理类对象。
invoke方法中的功能就是静态代理ProxyForFont等三个类中重写的getFont方法需要提供的功能。因为ProxyForFont和ProxyForMusic和ProxyForImage中相同的getFont方法实现,现在都在invoke方法中一次实现。
可见,动态代理和静态代理处理问题的思路没有差别,它们的差别在于创建代理时的代码不一样,动态代理有Java提供的框架支持,而静态代理需要开发者编码,所以动态代理节省了代码量,避免相同功能的重复代码。
总结
代理作为一种常见的设计模式,无论是静态还是动态,它们解决问题的思路上是没差的,但是动态代理由于框架的存在,比如Java自带的动态处理框架,可以帮助开发者节约代码量,提高开发效率。知道了这一点,在提到动态代理的时候,大家就不用疑惑为啥动态代理得这么玩了。
本文只是通过一个例子对代理有肤浅的介绍,等博主对反射有进一步了解后,再来分析下JDK的动态代理框架具体实现方式,以及cglib这种和JDK思路不完全一样的代理框架。
本文的诞生,要感谢Java动态代理封装、 Java JDK 动态代理(AOP)使用及实现原理分析、Java动态代理的作用是什么?和Java代理和动态代理机制分析和应用。
很惭愧,做了一点微小的贡献!
- 动态代理——从一窍不通到恍然大悟
- Android NDK 开发从一窍不通到入门
- Java深入浅出系列(四)——深入剖析动态代理--从静态代理到动态代理的演化
- Java深入浅出系列(四)——深入剖析动态代理--从静态代理到动态代理的演化
- 从静态代理到动态代理
- 从代理模式到Java动态代理
- MySql从一窍不通到入门(一)基本概念梳理
- Dagger2从入门到放弃再到恍然大悟
- Dagger2从入门到放弃再到恍然大悟
- Dagger2从入门到放弃再到恍然大悟
- Dagger2从入门到放弃再到恍然大悟
- 从静态代理到动态代理理解AOP
- MySql从一窍不通到入门(二)大数据量分页查询方法
- MySql从一窍不通到入门(三)连接查询、联合查询、子查询
- MySql从一窍不通到入门(四)Innodb/MyISAM和锁机制
- MySql从一窍不通到入门(五)Sharding:分表、分库、分片和分区
- MySql从一窍不通到入门(六)分表策略:取模/时间/哈希/区域
- MySql从一窍不通到入门(七)分区策略:HASH/RANGE/LIST/KEY
- List与String[]的相互转化
- lpc1788移植u-boot-2010.03之spi flash移植
- BZOJ 3365 [Usaco2004 Feb]Distance Statistics 路程统计 树的点分治
- JSP中使用UEditor遇到的问题
- Mybatis学习(二)
- 动态代理——从一窍不通到恍然大悟
- 黑马学习笔记--常用API 第二部分
- 检查回文字符串
- Chrome快捷键整理
- 【HEOI2014】bzoj3611 大工程
- IP、子网掩码、网关、dns的区别
- 【斜率优化】【决策单调】xjb讲课
- 【笔记】数据库--高级SQL
- Java 学习笔记5