动态代理浅析

一、简单来说:

  JDK动态代理只能对实现了接口的类生成代理,而不能针对类

  CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承)

二、Spring在选择用JDK还是CGLiB的依据:

   (1)当Bean实现接口时,Spring就会用JDK的动态代理

   (2)当Bean没有实现接口时,Spring使用CGlib是实现

   (3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class="true"/>)

三、CGlib比JDK快?

  (1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。

  (2)在对JDK动态代理与CGlib动态代理的代码实验中看,1W次执行下,JDK7及8的动态代理性能比CGlib要好20%左右。

以上内容转载自:https://www.cnblogs.com/bigmonkeys/p/7823268.html

这里主要介绍下jdk的动态代理

JDK最简单的创建代理对象的方法是用Proxy类的静态方法:

public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
public class ProxyTest {
    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Class<Human>[] interfaceArray = new Class[]{Human.class};
        Human h = (Human)Proxy.newProxyInstance(ProxyTest.class.getClassLoader(), interfaceArray, new MyInvocationHandler(new Student()));
        h.say();
    }
}
interface Human{
    void say();
}
class Student implements Human{

    @Override
    public void say() {
        System.out.println("student 说:");
    }
}
class MyInvocationHandler implements InvocationHandler {

    private Human human;
    MyInvocationHandler(Human human){
        this.human = human;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("清了清嗓子");
        human.say();
        System.out.println("大家好");
        return null;
    }
}

这是一个最简单的动态代理实现方式。

为了观察生成的代理类的内部结构,我们将生成的类导出

System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

如图,运行main方法后会生成一个$Proxy0的类,这个就是代理类。

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

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

    public final boolean equals(Object var1) throws  {
        ...
    }

    public final String toString() throws  {
        ...
    }

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

    public final int hashCode() throws  {
        ...
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.ryy.test.Human").getMethod("say", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

发现生成的代理类中有4个Method类型的属性,这四个属性分别是 equals()、toString()、hashCode()和say()。

当调用生成的$Proxy0实例的say()方法时,实际上是调用了该实例中的InvocationHandler成员变量中的

invoke(Object proxy, Method method, Object[] args)方法,将本身实例this和say()方法的Method对象作为参数传入。

大体流程:

1.创建一个需要被代理的方法的实例,这里是Student。

2.创建一个MyInvocationHandler类(InvocationHandlet的实现类)的实例,并将第一步创建的Student实例作为构造参数传入。

3.动态创建一个$Proxy0的类,它继承了Proxy并且实现了自己定义的接口Human,$Proxy0中的InvocationHandler的成员变量就是步骤2创建的实例。

4.调用步骤3创建的$Proxy()实例的say()方法,$Proxy0实例的say()方法调用MyInvocationHandler的invoke()方法以达到动态代理的效果。

注意:MyInvocationHandler的invoke(Object proxy, Method method, Object[] args)方法体中,不能调用proxy中的方法,如果调用会形成一种 proxy.say()->MyInvocationHandler.invoke()->proxy.say()->MyInvocationHandler.invoke()->......这种死循环,导致栈溢出。

最后给出大概的类关系图:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值