zoukankan      html  css  js  c++  java
  • 动态代理——从一窍不通到恍然大悟

    转载 :https://blog.csdn.net/u011026779/article/details/68954725

    前言

    动态代理是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 CachedProviderHandler(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代理和动态代理机制分析和应用。 
    很惭愧,做了一点微小的贡献!

  • 相关阅读:
    Tornado @tornado.gen.coroutine 与 yield
    ThreadPoolExecutor执行任务,异常日志缺失问题
    Mybatis关联查询<association> 和 <collection>
    Spring整合mybatis
    Jedis操作Redis--Key操作
    Jedis操作Redis--SortedSet类型
    Jedis操作Redis--Set类型
    同义词 “stop from”,“keep from”和“prevent from”的区别
    test
    Python win32gui调用窗口到最前面
  • 原文地址:https://www.cnblogs.com/mybatis/p/9391524.html
Copyright © 2011-2022 走看看