一文JDK动态代理的那点事儿

1、什么是代理

       代理其实就是讲事件的处理过程移交给第三方来处理,在我们的生活中也是比较常见的。经意不经意的都存在于我们的生活中,比如商人,商人做的就是需求的交易,比方说我们要买个碗,可能要去很远的地儿很多的地儿在选择出我们中意的,但是商家介入后,就省却了我们的时间和物质的付出成本,商人会帮我们去谈,去很多地儿去谈来后聚集到一起摆个摊让我们选择。

        我们现在的生活也是越来越方便了,想吃金百万的饭菜但是又是个懒人,那就叫个外卖好了,在家里坐等外卖小哥送餐上门岂不快哉,这里外卖小哥就是充当了我们的代理,帮我们送餐,话说出来了我们的订餐渠道是不是也是一种代理呢?答案是肯定的,订餐渠道其实就是我们的报菜的代理,同时也是外卖小哥的对接商家和接单的代理。

        总之,只要是有人际交往到处充斥着各种各样的代理,所以说代理模式并不是想我们想象的那样难以理解,而是我们日常习以为常的一种模式。

2、代理带来的好处

        既然代理总是不知不觉的充斥在我们的日常生活中,那么为什么会存在这种模式呢?给我们带来了哪些好处呢?没有代理模式存在的世界是怎么样的呢?接下来让我们来一点一点的分析揭晓代理模式的影响力。

       代理就是中介的一种体现,那么我们知道了中介的好处也就晓得了代理可以带来的好处了。中介是干啥的呢,起始就是为了方便快速的解决客户的需求而存在的,但是这里的需求不是说具体的需求,而是一个比较广义上的需求,是不是感觉很难理解,其实道理很简单,比如我们有租房需求,想找个房子租,但是还要考虑价格啊,哪里有空置房可以租住等等因素,那我们自己去想去的小区里挨家挨户去问也行,但是这种是不是既浪费我们宝贵的时间也麻烦不是,都不知道谁要往外租房子,价格也不好评估,自己还要谈价格,谈完价格在看看房子大小对比下性价比等等,天哪,这简直是遭罪啊,结果问了几天了可能就又一两个在往外出租,而且可能还不是我们想要的房子。

       好了,我们也兜了一圈了,也没有找到自己想要租住的房子,去链家看看吧,刚进门就发现有人来驱寒温暖,有凳子坐着,茶水有人给端过来放在面前,太舒服了,接下来切入正题,报出自己的需求,想要个多大的啊,价位多少啊等等,这时候就有人开始帮你梳理出来所有可能符合你预期的房子了,价格、户型、大小等等全都罗列出来了,自己选择吧,选择几个还有人领着直接对号入座方式的去现场勘查,我的个天啊,太快了,太方便了,这个小区没有还有别的小区的,天哪,这么多房源。到这里想必大家都晓得代理(中介)存在的意义了。

        总结:代理的好处就是我们随时可以替换掉我们想要的过程,只需要关注我们的结果就好了,而且过程怎么来的我们不关心,只关心我们的需求,单刀直入,一键解决岂不美哉,省时省力,最重要的是我们不用太担心里面是怎么流转的,屏蔽掉了我们不关心的细节。

3、软件开发中我们使用到的代理模式有哪些方式呢

       在我们平时的开发过程中,直接或者间接的都会使用到代理模式,如果没有使用过代理模式的话估计就是刚入行的写的代码数量基本上都是十指可数的节奏,那么如果你是个有点经验或者是经验老到的开发人员的,来说说平时使用到的代理模式有几种方式呢?

       就拿JAVA语言来说吧,java开发推崇的是面向接口开发,想用模板模式的话会弄出来个抽象类,在抽象类中写下必有的环节流程,其他的具体的实现流程交给实现类来处理,从而达到的高度灵活的可重用高扩展也符合开闭原则(对修改关闭,对新增开放)。

        一旦你书写过接口或者是抽象类那么就恭喜你,你会用代理模式了,虽然有可能你不理解其精髓起码用过了。首先来说说为啥你使用过接口或者是抽象类就代表着你使用过代理模式了,这就要说到JAVA的实例对象了,虽然你感觉自己在使用接口或者是抽象类,但是真正的实例实现却不是其本身,而是其子孙后代,在使用的时候你不需要关心你使用的接口的实现是什么,你也不关心,这就是静态代理模式,那么什么是静态呢?其实就是你的代理通过硬编码来实现的,无非就是你的子类流程流程上存在差异而已,那么什么情况下适合这种静态代理呢?当然是在我们的业务逻辑的某一处需要不同的处理策略或者是针对一个切面(内含很多职责)来说是有多种方案的情况下来使用的,这种其实主要是面向点来做的,这个点也可以理解成一种方案,也可以理解成针对某一个方法的不同处理。

         既然有静态的,那么就有动态的代理模式,什么是动态呢?动态其实不是我们所想的那样动态的生成代码,而是我们内置好的代码片段进行抽取,然后再执行其他方法的时候对我们来说感觉啥都没有变,其实对于JAVA实例来说其实就是一种我们没有定义过的具体的类实例,但是这个类不但包含了我们想要内置的代码片段,还有我们想要的具体实例的代码片段,进行了聚合从而让我们操作的对象有了我们想要的逻辑,那么你是不是想问是怎么做到的?什么时候做的?为何我明明看着里面没有这样的逻辑但是为啥会出这样的结果呢?下面让我们来说道说道动态代理的方式和原理以及在JAVA层面是怎么实现的。哦,对了,忘了说了,动态代理方案有两种:JDK自带了一种面向接口的动态代理,还有一种基于CGLIB可以面向类的动态代理。但是归根结底来说俩方案对我们来说都一样,都是为了实现动态代理,而且一般情况下我们在对大批量的行为做统一的处理的时候我们就会使用动态代理,因为如果有很多地儿要做一样的事儿,使用静态代理需要改动的地儿实现太多了,我们还是不要充当殷勤人了,还是老老实实的做个懒汉吧。

4、JDK中代理模式简介

       JDK中代理模式只能代理接口类,JDK为我们提供了两个需要操作的类(接口):Proxy和InvocationHandler。这里Proxy是生成代理类的入口工具类,其存在的目的就是针对代理实例的操作,比如代理类的生成,判断一个实例是不是代理类等等,而且为了提高性能,避免每次都重新生成一个代理类带来的开销,其内部还内置了缓存;InvocationHandler则是代理实例的真正核心,我们需要实现InvocationHandler来完成我们的业务逻辑功能,在代理实例中我们真正调用的方法其实就是InvocationHandler的invoke方法,代理类的作用就是调用InvocationHandler的invoke方法,可见InvocationHandler才是我们业务要关心的。好了,废话不多说,我们先做个简单的demo,看看JDK动态代理咋玩的。

       因为JDK代理只能对接口进行代理,那么我们是不是要先来一个接口呢?定义个啥接口呢,就简单的定义一个Person接口吧,里面有买菜的逻辑,如下:

public interface Person {

    void maiCai();

}

        好了,我们想在买菜的前后做点手脚,加点讨价还价的逻辑,咋办呢?这就用到了我们上面提到的InvocationHandler了,废话不多说,看看我们咋实现这个简单的家伙的:

public class MyInvocationHandler implements InvocationHandler {

    //被代理对象实例
    private Object object;
    //通过构造方法将被代理对象实例传递进来
    public MyInvocationHandler(Object object) {
        if (object == null){
            throw new IllegalArgumentException("被代理对象不能为空");
        }
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("开始讨价还价");
        Object result = method.invoke(object, args);//执行我们的买菜服务
        System.out.println("买完菜了,开始兴高采烈回家做饭");
        return result;
    }

}

       咦,是不是发现挺简单的,InvocationHandler就提供了一个方法,这个方法就是代理类要调用的方法,代理类会告诉我们代理类实例自身、以及要的代理的方法和方法需要的参数,可不吗,一个行为的入口可不就是一个方法吗,没错,就是这么简单搞定了。但是是不是发现还有一个东西需要我们处理,那就是被代理实例要告诉InvocationHandler,为啥要给个这玩意呢,很简单,就是为了执行被代理类真正的行为方法啊,要不然只执行我们增量的代码有啥意义呢,既然如此,那我们就来个实现吧:

public class Man implements Person{
    @Override
    public void maiCai() {
        System.out.println("直接付款买菜");
    }
}

       好了,万事俱备只差东风了,配料我们都准备好了,接下来就交给Proxy来帮我们做出一顿美味佳肴吧,咋做的我们就先不管了,我们来看看Proxy需要我们给的配料方式吧:

public static void main(String[] args) {
        //弄个加载器,让JVM晓得我们需要怎么加载我们的类
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        //初始化我们的被代理实例
        Person person = new Man();
        //主菜来了,这里就是生成我们的代理类实例的入口了,我们需要告诉他怎么找到我们类,而且还要告诉他我们类是啥,然后还要告诉他我们增加的逻辑是啥
        Person proxyInstance = (Person) Proxy.newProxyInstance(classLoader, new Class[]{Person.class}, new MyInvocationHandler(person));
        //代理类生成好了,来下个命令让他执行下吧
        proxyInstance.maiCai();
    }

       好了,我们运行下,看看我们的成果吧:

开始讨价还价
直接付款买菜
买完菜了,开始兴高采烈回家做饭

       好了,看到这里你是不是已经会使用JDK生成个动态代理了?如果不会的话就得考虑下自己的智商适不适合干这行了,好了这节课就给同学们分享到这里吧,接下来我会告诉你JDK动态代理是怎么做到这么捡漏但是很神奇强大的功能的,敬请收看下节精彩讲解吧

5、JDK动态代理源码分析

       InvocationHandler是JDK给我们预留的让我们自定义实现来定义我们的业务逻辑的,在JDK内部就是一个单纯的业务接口,是对方法调用的一种抽象形式,和我们自义定的接口没有啥本质上的区别,总归就一句话:这是需要我们来实现的接口,具体怎么实现的,JDK不管。

       Proxy类中的源码才是需要我们进行探索的,也是JDK动态代理核心实现的。如果让你实现动态代理的话(假设你已经足够的了解了class字节码的结构)你会怎么做呢,如果你会JDK的反射的话相信剩下的就是动态代理类的生成的问题了,那么好,废话不多说,让我们来看看JDK中是如何实现的动态代理的生成吧,先上来一段JDK中的代码过过瘾吧,起始就几行代码:

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException
    {
        Objects.requireNonNull(h);
        final Class[] intfs = interfaces.clone();
        //通过classLoader和需要实现的接口来生成实现了所有的接口的方法类
        Class cl = getProxyClass0(loader, intfs);
        try {
            //获取上面生成的类的带有InvocationHandler类型参数的构造方法
            final Constructor cons = cl.getConstructor(new Class[]{ InvocationHandler.class });
            //通过构造方法实例化生成的代理类,并将InvocationHandler实例通过构造方法传递进去
            return cons.newInstance(new Object[]{h});
        } catch (Exception e) {            
            throw new InternalError(e.toString(), e);
        }
    }

上面就是动态代理的核心代码了,是不是惊呼一片,就这点破玩意儿竟然这么强悍,是的,就是这点破玩意完成了IT的一次变革。接下来我们绘制一个图来看看有哪些流程吧:

一文JDK动态代理的那点事儿_第1张图片

        还真是三步走,那么在这三步中你觉得哪些步骤有挑战呢?先来说第二步吧,这不就是反射通过Class元信息获取到构造函数实例吗,起始就是一个内存级的检索,你还觉得复杂吗,但凡有点反射的底子就晓得这步起始还是挺简单的;好,那我们来说说第三步吧,这步通过构造函数来构造一个实例,但是这里的构造函数确实一个带有InvocationHandler实例的构造函数,这也是反射里的小知识点,这里也没有啥可说的,相信你也不觉得费劲儿;那么我们来看看第一步吧,如果是你来实现这块的话,你会怎么做呢?首先我们来分析一下,这里可能会涉及到的问题:1、动态生成一个class实例,如果每次执行都要动态生成一次,岂不是性能上让人哭晕在厕所了,那么既然先在做到共享的话,那就缓存是首选了,通过缓存在提高下性能吧,继续分析,还有啥呢?那当时是怎么来生成class了,加入你熟悉class字节码生成它不是问题,那么问题又来了,既然JDK中只是对接口进行代理,那么应该怎么对接口进行代理呢?生成的代理类既然要实现这些接口,那么应该怎么来实现这些接口方法呢?每个方法我们晓得都是一种行为的过程的封装,动态代理也不例外,只不过JDK的动态代理类的方法的实现是由JDK开发人员帮我们规划好的特定的行为。

        好了,既然第1不生成class动态代理类实例是个难点,那么我们就来根据我们上面的分析来一步一步的分解掉上面的过程来具体看看JDK生成动态代理类的过程吧:

一文JDK动态代理的那点事儿_第2张图片

上面就是JDK中生成class类实例的入口,就是通过我们给定的接口和类加载器来生成的,接下里我们来看看getProxyClass0里面干了点啥吧:

一文JDK动态代理的那点事儿_第3张图片

哇塞,是不是够简单,一个Cache搞定了,就这么一行代码,哈哈,别急,你就没有疑问吗?顾明意思,缓存的目的是为了提高检索性能,对应高并发等存在的,并不是用来生成动态代理类的,那么你会有疑问这里就是直接从缓存里拿的啊,好像是一开始JDK就知道我们想要的代理一样,为了解答你的疑问,我们就来具体研究下这个缓存吧,让你打消掉心中的疑问和不解。我们先来看看这里使用的是啥缓存吧

一文JDK动态代理的那点事儿_第4张图片

哇咔咔,这是啥缓存,咋没有见过,别急别急,看着挺复杂结构的缓存我们不能打退堂鼓,来一点一点肢解掉他吧,在肢解掉他前我们先来研究下WeakCache是和神圣吧,WeakCache研究明白了,那么我们也就晓得WeakCache是怎么抽象做到的通用的了,废话不多说,开始吧,咦!!!不对啊,我们这篇文章是讲解代理模式的啊,这和缓存是风马牛不相及啊,硬说有联系那也是JDK动态代理类使用到了WeakCache缓存而已啊,并非强制依赖吧,大不了我不在乎这点性能,每次都生成一次呗,那既然这样的话我就另提一个文章来专门说WeakCache吧,了解的直接跳过吧,不懂的话就点这里:一文点破WeakCache那点事。

上面的缓存代理获取方式可见,一级缓存Key是ClassLoad生成的CacheKey作为键;二级缓存Key则是通过ClassLoader实例和被代理接口生成的唯一的约束,这里因为相同接口在不同ClassLoader下生成的实例是不一样的。在学习了WeakCache原理后,接下来我们就来看看动态代理内的SubKeyFactory和ValueFactory的实现,了解其原理即可:

SubKeyFactory的实现如下:

     /**
     * 通过ClassLoader和被代理接口数组生成唯一的二级缓存Key
     */
    private static final class KeyFactory
        implements BiFunction[], Object>
    {
        @Override
        public Object apply(ClassLoader classLoader, Class[] interfaces) {
            //这里是根据接口数量来生成的SubKey作为二级缓存的Key,这里可见接口数量不同生成的Key不同
            //这么多Key其内部主要是equal和hashCode实现方式不同,方便进行比较区分是否相同
            switch (interfaces.length) {
                case 1: return new Key1(interfaces[0]); // the most frequent
                case 2: return new Key2(interfaces[0], interfaces[1]);
                case 0: return key0;
                default: return new KeyX(interfaces);
            }
        }
    }

 ValueFactory:在JDK动态代理中ValueFactory中保存有生成代理类真正的业务逻辑:

1、我们先来了解下代理类真正的生成过程流程

一文JDK动态代理的那点事儿_第5张图片

2、下面我们来具体看下ValueFactory的具体实现

/**
     * 作为CacheKey的ValueFactory来完成代理类的生成工作
     */
    private static final class ProxyClassFactory
        implements BiFunction[], Class>
    {
        //代理类前缀
        private static final String proxyClassNamePrefix = "$Proxy";

        // 生成代理类全局唯一的数字自增器
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class apply(ClassLoader loader, Class[] interfaces) {

            Map, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            //如下循环对所有的接口类进行验证
            //需要验证的问题:1、接口中没有重复接口 2、给定的接口可以通过给定的ClassLoader加载
            for (Class intf : interfaces) {
                Class interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }
            //生成代理类的包路径以及代理类访问权限控制,包路径生成规则如下:
            //1、如果接口全部为public的则代理类为public final,否则为final
            //2、如果所有接口都是public的则代理类包路径com.sun.proxy.前缀;否则需要所有接口在同一个包内,且代理类包路径为接口路径
            String proxyPkg = null;
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
            for (Class intf : interfaces) {
                int flags = intf.getModifiers();
                //这里如果发现有接口非public访问权限则进行代理类访问权限和代理类包路径特殊处理
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }
            if (proxyPkg == null) {
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }
            //为了保证代理类唯一,这里使用原子操作递增方式获取VM中全局唯一序号
            long num = nextUniqueNumber.getAndIncrement();
            //这里代理类包路径前缀+$Proxy+全局唯一序号作为代理类的名称
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * 通过ASM来生成代理类字节码
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                //通过本地方法将字节码加载为代理类Class实例
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

好了,下面就让我们来看看JDK动态代理生成的Class信息吧:

 一文JDK动态代理的那点事儿_第6张图片

 

6、总结

这里动态代理和JDK的动态代理原理讲解到这里吧,想了解CGLIB动态代理原理请看我写的一文道破CGLIB动态代理

你可能感兴趣的:(动态代理,JDK代理原理,java)