vlambda博客
学习文章列表

10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

一、代理的定义

既然提到了代理,那么代理到底体现在哪?比如我在说静态代理,就需要说清楚什么是静态?说动态代理,就要明确指出动态在哪了?还有在动态代理中,为什么jdk动态代理必须是实现接口,而cglib动态代理却不要?

很多时候,程序出现了bug,我们都喜欢查看源码,从源码的角度解析问题的起因,这是一个很好的习惯,可是转而向产品经理表述的时候,却说源码里就是这样写的,这样看源码本来是一件好事,结果却变成了怼人的工具,别说产品经理听到源码两字头疼,久而久之程序员也逐渐偏离初心,丢掉了最初编码的那种乐趣。

归根结底,在源码和产品经理之间,我们程序员也要做好代理的作用,用大白话传达源码思想,工程思维和产品思维的碰撞,推动业务的发展。

代理是个动词,在词典中的解释是受当事人的委托,代表当事人行使某种权利,做某种事情,如签合同、诉讼等,其实就是帮人做事。

历史上最为著名的代理就是垂帘听政,皇帝坐在前,太后坐其后,中间隔着一个帘子,太后说啥,皇帝学着说。听上去还挺麻烦的,倒不如扯开帘子,太后直接称帝。

历史上女皇武则天就这么做了,结果有违“三纲五常”颇受后人争议,人家英国女王就没有这种“夫为妻纲”的规矩,不也当的好好的。到清朝,慈禧太后大权在握,不想放权又不想引起争议,就搞了个“垂帘听政”,其实谁上谁下的,对老百姓来说没啥区别,只要能过上好日子就行。


1.1 现实中的代理

在我们的现实生活中,代理无处不在,卖票的,明星经纪人,律师等,在具体展开代理之前,还要说清楚代理和中介的关系,一眼看上去,好像也没啥区别。非也,代理强调的是个体的代替,中介强调的是信息的传递。比如代理律师替客户在法庭上打官司,房产中介将房源信息匹配给买房客户。

对需要打官司的客户来说,代理律师是加强版的自己,不仅有自己的诉讼要求,还有专业的法律知识和出色的口才,要这样的人帮自己打官司,才有胜算。

房产中介做的是信息差生意,卖方不知道谁来买房,买方不知道谁在卖房,中介说我知道,听我的吧。这就叫中间商赚差价。最近谢娜张杰夫妇买房跳单上了热搜,跳单就是越过中介直接找房东签约,中介小伙满脸委屈的做了回好人好事。

互联网时代,绝对的信息差已不存在,从单打独斗升级到了平台,比如电商领域的某宝某东拼夕夕,房产领域的某链等。

1.2 编码中的代理

言归正传,在编码世界里,什么是代理呢?

10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

在图中,代理类代理了实体类,实体类和代理类有一个共同的接口,之所以这样设计,是因为代理不能脱离实体,他们之间必须有关系,也就是代理是为实体服务的,如果连实体的具体需求都不知道,代理也就代理了个寂寞。

这个共同的抽象接口就像代理和实体之间的一个合约,合约上规定代理需要对实体的那些行为进行服务,代理拿到合约就知道该为实体做什么了,并且针对这个“做什么”,代理有很强的专业素养,围绕这个“做什么”做一些额外的工作,这个过程就是增强。

代理就成了升级版的实体。实体的力量得到了延伸。客户直接接触代理也就相当于间接接触实体。当然,客户也可以直接接触实体,这样就增加了复杂性,比如,一个公司有研发部门和销售部门,客户有需求可以直接找研发部门做,可是研发部门只会研发,产品的价格,服务并不是太懂,这样的后果就会浪费大量的时间在非研发上扯皮,专业的事交给专业的人来做,剩下的事情交给代理做,是一个比较合理的分配。

二、静态代理

2.1 静态代理案例

最近王力宏离婚案闹的沸沸扬扬,当前主要纠纷点在孩子的探视权上,王力宏向美国法院提交了11项证据,争取探视权。李靓蕾提交了7项证据,回应法院。那么在这个过程中,王力宏必然会雇用专业的律师负责这个案子。这样实体是王力宏,代理是律师。王力宏雇用律师签订合作合同,合同要求律师帮王力宏赢得孩子探视权。

10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

SeeChildSuit接口规定了王力宏和律师的诉讼请求

public interface SeeChildSuit {
    public void seeChild();
}

Wanglihong类具体规定了诉讼的具体内容,就是想要看看孩子

public class Wanglihong implements SeeChildSuit {
    @Override
    public void seeChild() {
        System.out.println("想要看看孩子");
    }
}

Lawer类实现了SeeChildSuit接口,按照合同规定的内容,运用自己的法律知识和专业技能,帮王力宏争取孩子探视权。围绕案件的诉讼请求,律师需要准备证据,提交法庭,陈述诉讼请求,并根据证据需要当庭进行辩论,这些额外的工作都是律师来做的。

public class Lawer implements SeeChildSuit {

    SeeChildSuit seeChildSuit;

    public Lawer(SeeChildSuit seeChildSuit) {
        this.seeChildSuit = seeChildSuit;
    }
    @Override
    public void seeChild() {
        System.out.println("律师准备证据并陈述诉讼请求:");
        seeChildSuit.seeChild();
        System.out.println("律师进行法庭辩论");
    }
}

开庭审理

public class TestCase {
    public static void main(String[] args) {
        System.out.println("法庭开庭");
        SeeChildSuit seeChildSuit = new Lawer(new Wanglihong());
        seeChildSuit.seeChild();
    }
}


10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

从中可以看出律师代理了王力宏处理了这个案件,并且凭借自己多年的工作经历和专业素养,为案件的胜诉做了十足的准备,祝他早日能见到自己的儿女。以上代理就是静态代理的过程,它的静态是指代理类在代码执行之前就已经确定,增强代码和代理类的生成耦合在一起,后期有任何变化,修改这个代理类就行。

进行到这里,似乎看起来很完美,王力宏委任的律师叫Bonnie E. Rabin,从业35年,是资深的婚姻和家庭法领域的专家律师。该律师在处理复杂的财务问题上拥有丰富的经验。

可是鸡蛋也不能只放在一个篮子里,万一开庭当天,Bonnie律师在半路堵车了,其他律师短时间内还没法接手这个案子,法庭上当事人到了,律师没到,王力宏就欲哭无泪了。

在竞技体育中,有首发队员和替补队员,首发队员都是一些明星队员,他们过往有令人瞩目的成绩,可是这并不能保证每一场都能胜利,这和临场发挥有很大的关系,因此一场比赛中状态良好的替补队员换下状态不好的主力队员仍能赢得比赛。

同样对于王力宏最稳妥的方式就是聘请一个律师团,Bonnie律师可以作为首发,其他律师作为替补。哪个律师能上场不重要,重要的是赢得官司。

动态代理则实现了以上想法,动态代理将代理的生成和增强代码分离开来,所谓的代理,说到底就是给原来的方法增强功能,增强代码就像是诉讼中的关键证据,在法庭上赢得诉讼的核心是关键证据,与委托哪个律师关系不大,这也符合实际,法律就是讲证据的。

代理的目的是构造一个和被代理的对象有同样行为的对象。

jdk动态代理通过实现相同的接口,使代理类和被代理类有相同的行为。

2.2 静态代理的缺点

原来的代理不仅持有实体类的代理方法,还有增强方法,这样会耦合在一起,在静态代理中,代理类中围绕代理的目标方法写增强方法,这样就会造成当前的代理类是高度耦合实体类的,仿佛就像是对实体类的个人定制,那么其他实体类就无法复用这个代理类,只能再按照同样的方法,高度定制关于这个实体类的代理类,如果1000个实体类都需要代理,那么就要定制1000个代理类。

如果将代理类和增强方法分开,那么就可以复用代理类了,改变的只是增强方法的实现,这样无论增强方法怎样改变,代理类不会发生变化,把变化转移到增强方法中。增强方法的具体实现根据具体业务内容进行调整,具体问题具体分析,这种灵活的解决问题的方式就是动态代理。

三、动态代理

3.1 JDK动态代理?

静态代理之所以是静态,是因为在程序运行前,代理类是需要程序员写好,而动态代理,代理类无需程序员写了,在运行时会自动生成。

那么自动生成的代理唯一的目的就是获得被代理类的实例,这部分不涉及具体的业务,当然可以在底层进行响应的实现,但是涉及具体业务的地方也就是代理中增强的代码,则需要程序员自己实现,这些增强代码在代理调用被代理类的时候加上即可,这样既达到了代理实例,又达到按需配置增强代码的目的。

我们说了那么多的增强的代码,那么在动态代理中具体的应该怎样实现呢?增强代码需要放进一个类中,这个类并不是普通的类,它需要实现InvocationHandler接口,它还持有被代理的对象,在InvocationHandler中有一个invoke方法,这个invoke方法拿到被代理的对象,通过反射可以执行被代理中的方法。通过反射执行被代理对象的方法前后,可以写上一些增强代码。

这里是不是有点像SpringAOP中的环绕通知,实际上在SpringAOP中,环绕通知方法就是增强方法,关于SpringAOP参看:

动态代理的增强方法如下:

public class LawerInvocationHandler<T> implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("律师准备证据并陈述诉讼请求:");
        Object result = method.invoke(target, args);
        System.out.println("律师进行法庭辩论");
        return result;
    }
}


3.1.1 案例

公共接口

public interface SeeChildSuit {
    public void seeChild();
}

被代理对象

public class LawerInvocationHandler<T> implements InvocationHandler {
    T target;
    public LawerInvocationHandler(T target) {
        this.target = target;
    }

    /**
     * proxy:代表动态代理对象
     * method:代表正在执行的方法
     * args:代表调用目标方法时传入的实参
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("律师准备证据并陈述诉讼请求:");
        Object result = method.invoke(target, args);
        System.out.println("律师进行法庭辩论");
        return result;
    }
}

测试,先生成代理对象,再调用目标方法

public class TestCase {
    public static void main(String[] args) {
        System.out.println("法庭开庭");

        // 获取增强类
        InvocationHandler lawerHandler = new LawerInvocationHandler<SeeChildSuit>(new Wanglihong());


        // 传入被代理类类加载器,被代理类接口,和增强类,获得代理类
        SeeChildSuit seeChildSuitProxy = (SeeChildSuit) Proxy.newProxyInstance(SeeChildSuit.class.getClassLoader(), new Class<?>[]{SeeChildSuit.class}, lawerHandler);


        // 使用代理类执行目标方法
        seeChildSuitProxy.seeChild();

    }
}


10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

3.1.2 JDK动态代理原理

以上只是准备好了增强代码和被代理类目标方法的调用,也就是InvocationHandler中的invoke方法,那么会在什么地方用上invoke方法呢?

其实,我们可以从结果反推来看,我们最终获得了代理对象,通过代理对象调用被代理类的方法,这个被代理类的方法肯定在代理类中存在,这样就推断出,代理类和被代理类有相同的方法,在以往的编程经验中,要么这两个类实现了接口获得相同的方法,或者这两个类继承了同一个父类获得相同的方法,无论哪种,这两个类都是有联系的。

这个相同的方法,在被代理类中是完备实现的,在代理类中,也要想办法获得被代理类的完备实现,那么被代理类是怎样获得被代理类方法的完备实现的呢?我们从代码中找一找这个问题的答案吧。

这是生成代理的代码,从代码中可以看出,代理对象是通过Proxy的newProxyInstance方法获取的这个newProxyInstance方法很有意思,它有3个参数,第一个是类加载器,第二个是代理类的接口,第三个是实现InvocationHandler的增强类

public class TestCase {
    public static void main(String[] args) {
        System.out.println("法庭开庭");

        // 代理类class文件存入本地磁盘,使用反编译工具查看生成的代码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\wanglihong_gdk");

        // 获取增强类
        InvocationHandler lawerHandler = new LawerInvocationHandler<SeeChildSuit>(new Wanglihong());

        // 传入被代理类类加载器,被代理类接口,和增强类,获得代理类
        SeeChildSuit seeChildSuitProxy = (SeeChildSuit) Proxy.newProxyInstance(SeeChildSuit.class.getClassLoader(), new Class<?>[]{SeeChildSuit.class}, lawerHandler);

        // 使用代理类执行目标方法
        seeChildSuitProxy.seeChild();
    }

}

继续追踪newProxyInstance方法,其中getProxyClass0方法让人坐不住了,这不就是生成代理类的方法吗

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,
                          InvocationHandler h) throws IllegalArgumentException {
  
        Class<?> cl = getProxyClass0(loader, intfs);
       。。。
    }

继续追踪getProxyClass0方法,

private static Class<?> getProxyClass0(ClassLoader loader,
                                       Class<?>... interfaces) {
    return proxyClassCache.get(loader, interfaces);
}

原来代理类是放在一个缓存中的

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
    proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());


有点上头了继续最终这个缓存的get方法,其他的逻辑不废话了,具体就是从缓存中获取代理类,没有则通过apply方法创建代理类

public V get(K key, P parameter) {
   
    Object subKey = Objects.requireNonNull(subKeyFactory.apply(key, parameter));
    。。。
}


创建代理类,拨开云雾见月明,这里就是为创建代理类做一些准备工作,比如给代理类取个名字,代理类的接口,以及代理类的访问控制符最后落脚在generateProxyClass方法上

public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
。。。
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
       
}


在generateProxyClass方法中我们才知道,所谓的创建代理类就是生成代理类字节码的过程。相比于静态代理在程序运行就已经生成字节码,这里是在程序运行中生成字节码,有点像在运行的马车上换车轮。

public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
    ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
    final byte[] var4 = var3.generateClassFile();
    。。。

    return var4;
}


针对这个字节码的生成过程没什么可探究的,因为这不是我们研究的重点,看源码就有这样的困扰,一开始为了一个问题开始追踪源码,追着追着就目眩神迷,不断的惊叹代码设计的精妙,在连连张开下巴的同时,就已经忘了当初为什么出发了。

所以在看源码的时候,需要时时提醒自己,不要过早的关注细节,理清脉络,保持初心。所以我写源码分析的部分不会大段大段的抄写源码,只留下源码分析的路线,心中有路,就把怕沿途的风景绊住脚步。

我们需要知道代理类是怎么调用被代理类的方法的,那么就需要知道代理类具体长什么样子,当前已经获得了生成的代理类的字节码,或许秘密就在这个字节码中,借助反编译工具,反编译一下这个字节码

反编译字节码

public final class $Proxy0 extends Proxy implements SeeChildSuit {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m0;


    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }



    public final void seeChild() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }



    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.handlecode.jdk.SeeChildSuit").getMethod("seeChild");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}


这里可以看到,代理类实现了被代理类的相同接口,他们的共同方法在代理类中也会有体现,如seeChild方法,这个方法调用的是InvocationHandler的invoke方法,这里就与实现InvocationHandler接口的增强类呼应上了,增强类负责invoke方法的定义,生成的代理类中则会调用这个invoke方法。

无论增强类的invoke方法结合业务怎样定义,在代理类中只需调用即可,这样就完美的将增强方法和代理分开,既有变得地方也有不变的地方,互不影响,相对静态代理的牵一发动全身要好很多。

静态代理将代理和增强代码混在一起,增强代码都是和业务相关的,是高度定制的。这样的代理类无法复用在其他需要代理的类上。动态代理则将代理类和增强代码分开了,增强类里定义一个方法,代理类在生成的时候会调用这个方法。这样我们在使用代理的时候。

3.1.3 执行步骤总结

10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

1.准备好增强类,增强类实现InvocationHandler接口,在invoke方法添加增强代码以及调用被代理类的目标方法。

2.生成代理类,增强类以参数的方式传入生成代理类的方法中,在生成的代理类中,拥有被代理类相同的目标方法签名,只是方法体是调用增强类的invoke方法。

3.调用目标方法。调用代理类的目标方法,实际调用被代理类的目标方法。

3.2 cglib动态代理

将被代理类作为代理类的父类,使两个拥有相同的行为。

cglib动态代理相对于jdk动态代理来说,它代理的目标类没有实现任何接口,而是将目标类作为父类,通过字节码的方式生成子类作为代理类。因此目标类必须是可继承的,如果目标类声明成final,就无法使用cglib动态代理了。

同时目标类中的方法也必须声明成public和protect,总之,目标类能被继承,目标方法能被重写即可。其实,本质上这和jdk动态代理的目的是一样的,都是为了获得目标类的一个副本。

3.2.1 案例

被代理对象

public class Wanglihong {
    public void seeChild() {
        System.out.println("想要看看孩子");
    }
}

增强类

public class LawerMethodInterceptor implements MethodInterceptor{

    @Override
    public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("律师准备证据并陈述诉讼请求:");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("律师进行法庭辩论");
        return object;
    }
}

生成代理类,执行目标方法

public class TestCase {
    public static void main(String[] args) {
        System.out.println("法庭开庭");

        // 代理类class文件存入本地磁盘,使用反编译工具查看生成的代码
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, System.getProperty("user.dir")+"/cglib/" );

        Enhancer enhancer = new Enhancer();

        // 设置代理类的父类
        enhancer.setSuperclass(Wanglihong.class);

        // 通过回调函数,将增强类引入到生成的代理类中
        enhancer.setCallback(new LawerMethodInterceptor());

        // 生成代理对象
        Wanglihong proxy = (Wanglihong)enhancer.create();

        // 通过代理对象调用目标方法
        proxy.seeChild();
    }
}


10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

3.2.2 cglib动态代理原理

在jdk动态代理的时候,我们探究了代理的生成过程,cglib动态代理也是同样的动态生成代理,那么就探究一下它的具体生成过程吧。

3.2.2.1 增强类

首先我们需要一个类作为增强类,这个增强类实现了MethodInterceptor接口,MethodInterceptor中有intercept方法,具体的增强代码写在这个intercept方法里即可,值得关注的是intercept方法有4个参数,第一个是代理对象,第二个是目标方法,第三个是目标方法参数,第四个是代理对象生成的代理方法。

public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable ;


增强类里除了增强代码还有通过反射调用目标方法,增强类中通过代理对象生成的代理方法反射执行父类的方法。在intercept方法中,有个很奇怪的地方是,方法参数既有目标方法Method也有代理方法MethodProxy,最后使用代理方法MethodProxy进行反射,如果使用Method方法进行反射会怎样呢?

其实生成的代理类是被代理类的子类,使用Method进行反射,执行的只是生成的代理类中的方法,而不是父类中的方法,我们代理的目的就是对父类中的方法进行代理,这样子类方法中通过反射调用自己就陷入了死循环。使用代理方法MethodProxy调用invokeSuper可以反射到父类的方法,就准确完成了目标。

public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("律师准备证据并陈述诉讼请求:");
        Object object = methodProxy.invokeSuper(sub, objects);
        System.out.println("律师进行法庭辩论");
        return object;
    }

3.2.2.2 生成代理类
public static void main(String[] args) {
    Enhancer enhancer = new Enhancer();
        // 设置代理类的父类
        enhancer.setSuperclass(Wanglihong.class);
        // 通过回调函数,将增强类引入到生成的代理类中
        enhancer.setCallback(new LawerMethodInterceptor());
        // 生成代理对象
        Wanglihong proxy = (Wanglihong)enhancer.create();
        // 通过代理对象调用目标方法
        proxy.seeChild();
}

理类的生成是通过封装的ASM框架实现的,追踪create方法,经过如下调用:

net.sf.cglib.proxy.Enhancer#createHelper
net.sf.cglib.core.AbstractClassGenerator#create
net.sf.cglib.core.AbstractClassGenerator#get
net.sf.cglib.core.AbstractClassGenerator#generate
net.sf.cglib.core.DefaultGeneratorStrategy#generate
net.sf.cglib.transform.ClassReaderGenerator#generateClass
org.objectweb.asm.ClassReader#accept


反编译字节码,可以看到生成的代理类继承父类HelloService,同样重写了父类的方法sayHello在这个重写方法中,我们可以看到调用了实现MethodInterceptor接口的增强类中的intercept方法

public class HelloService$$EnhancerByCGLIB$$e099f798 extends HelloService implements Factory {
   private static final MethodProxy CGLIB$sayHello$0$Proxy = null;
   CGLIB$sayHello$0$Proxy = MethodProxy.create(cls3, cls, "()V""sayHello""CGLIB$sayHello$0");

    public final void sayHello() {
        MethodInterceptor methodInterceptor = this.CGLIB$CALLBACK_0;
        if (methodInterceptor == null) {
            CGLIB$BIND_CALLBACKS(this);
            methodInterceptor = this.CGLIB$CALLBACK_0;
        }
        if (methodInterceptor != null) {
            methodInterceptor.intercept(this, CGLIB$sayHello$0$Method, CGLIB$emptyArgs, CGLIB$sayHello$0$Proxy);
        } else {
            HelloService$$EnhancerByCGLIB$$e099f798.super.sayHello();
        }
    }

}


值得注意的是这里的MethodInterceptor类是通过setCallback方法引入的,而jdk动态代理中是通过newProxyInstance方法的参数引入的,无论哪种方式,生成的代理类必须拥有增强类,才可以调用增强类中的方法,这是动态代理分离代理类和增强代码的关键。

3.2.2.3 fastclass机制

在生成的代理类中有一句代码,第1个参数是被代理对象,第2个参数是代理对象,第3个参数是入参类型,第4个参数是被代理方法名,第5个参数是代理方法名

private static final MethodProxy CGLIB$sayHello$0$Proxy = MethodProxy.create(cls3, cls, "()V""sayHello""CGLIB$sayHello$0");


CGLIB 0$Proxy作为MethodProxy传递给增强类中intercept方法调用被代理类目标方法之前也探讨过,通过MethodProxy可以获取到父类的方法进行执行,那么在MethodProxy中是怎样获取父类方法的?这里显然是创建了MethodProxy,增强类的intercept方法中使用MethodProxy的invokeSuper方法获得父类的方法。那就分两步走:

「1.创建MethodProxy」

net.sf.cglib.proxy.MethodProxy#create

public static MethodProxy create(Class c1, Class c2, String desc, String name1, String name2) {
    MethodProxy proxy = new MethodProxy();
    proxy.sig1 = new Signature(name1, desc);
    proxy.sig2 = new Signature(name2, desc);
    proxy.createInfo = new CreateInfo(c1, c2);
    return proxy;
}

create方法主要用来实例化MethodProxy,Signature实体记录了代理类和被代理类以及他们所对应的方法入参,CreateInfo实体记录了代理类和被代理类。

「2.通过MethodProxy获得被代理类方法」

在增强类中intercept方法中MethodProxy调用invokeSuper方法 net.sf.cglib.proxy.MethodProxy#invokeSuper

public Object invokeSuper(Object obj, Object[] args) throws Throwable {
    try {
        init();
        FastClassInfo fci = fastClassInfo;
        return fci.f2.invoke(fci.i2, obj, args);
    } catch (InvocationTargetException e) {
        throw e.getTargetException();
    }
}


「2.1 init方法」

在获取被代理类方法前,执行了init方法init方法的主要目的是给FastClassInfo实体赋值,由代码可知FastClassInfo封装了两个FastClass实体,分别对应代理类和被代理类,并给这两个FastClass实体设置索引值。

值得注意的是,helper方法是将代理类和被代理类封装的方法,封装之前需要在缓存中获取到这两个类,如果缓存中没有,就要借助ASM生成新的fastclass字节码。所以cglib动态代理在启动的时候就比JDK动态代理慢,不仅要生成代理类,还要生成对应的FastClass,正是因为前期做好了准备,在代码执行的时候就比jdk快了。

private void init()
{
    if (fastClassInfo == null)
    {
        synchronized (initLock)
        {
            if (fastClassInfo == null)
            {
                CreateInfo ci = createInfo;

                FastClassInfo fci = new FastClassInfo();
                fci.f1 = helper(ci, ci.c1);
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(sig1);
                fci.i2 = fci.f2.getIndex(sig2);
                fastClassInfo = fci;
                createInfo = null;
            }
        }
    }
}

private static class FastClassInfo
{
    FastClass f1;
    FastClass f2;
    int i1;
    int i2;
}


「2.2 invoke方法」

在init方法中,实例化了FastClassInfo实体,那么在invoke方法中,就可以按照FastClassInfo设置的索引直接调用FastClassInfo实体中保存的被代理类的方法省去了反射调用,比jdk动态代理快。所以cglib动态代理的执行速度还是比较快的。

3.2.3 执行步骤总结

10分钟趣析Java中的静态代理,JDK动态代理和cglib动态代理(带源码)

1.创建增强类,实现MethodInterceptor接口,在intercept方法中添加增强代码以及调用目标方法

2.生成代理类,在代理类中,通过回调属性的设置获取增强类,代理类中的目标方法实际调用的是增强类的intercept方法

3.调用目标方法。这里通过fastclass机制将代理类和被代理被都放进缓存里,调用目标方法实际调用的是缓存中的被代理类中的目标方法。

最后奉上本文涉及到的源码https://gitee.com/handlecoder/spring-demo-all,不止是源码,运行demo可生成字节码文件。

经历的痛苦愈多,体会到的喜悦就愈多。 我是句柄,我们下期见!

·················END·················



- 推荐阅读 -


‘点击’ 上面标题即可查看往期精彩内容