java动态代理 ----cglib可以嵌套代理

代理机制

本文会从生成的代理对象的字节码角度浅析jdkProxy和cglib代理的区别。以及cglib如何做到嵌套代理

JDKProxy

JDKProxy是基于接口实现的。
利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类$Proxy.

对于一个接口A的对象a来说,只要这个对象a获取到了该接口实现类B的代理,则对于接口的实例对象来说,是可以调用到实现类B所实现的接口的方法。而在接口中没定义的方法是不能调用的.

 		Class<?> klass = object.getClass();
        ClassLoader classLoader = klass.getClassLoader();
        Class<?>[] interfaces = klass.getInterfaces();

        return Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //在执行方法之前可以做一些事情 例如对参数的检查 从而决定方法是否真正执行
                Object result = method.invoke(object, args);
                //在执行方法之后可以做一些事情
                return result;
            }
        });

CGLibPRoxy

  public  static  <T> T getProxy(Object object) {
        Class<?> aClass = object.getClass();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(aClass);
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //切面
                Object result = method.invoke(object, objects);
                //切面
                return result;
            }
        });

        return (T) enhancer.create();
    }

对于CGLib代理模式,是无需要基于接口来实现的。
代理类相当于继承于被代理类
CGlib是对类进行代理,而jdk是对接口的实现类进行代理。
对于一个类A的对象a来说,只要a取得了自己的代理,则a执行自身的方法的时候就可以面向切面编程。a可以执行A的除了private ,static 的所有方法

案例
有这样一个接口

public interface IDao {
    public void select();
    public void insert();
}

和这样一个实现类

public class Dao implements IDao{
    
    @Override
    public void select() {
        System.out.println("select 1 from dual:");
        insert();
    }

    @Override
    public void insert() {
        System.out.println("insert into ...");
    }

    public final void delete() {
        System.out.println("delete from ...");
    }
}
  1. 采用jdk proxy
 public static void main(String[] args) {
        System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        IDao dao = (IDao) Proxy.newProxyInstance(
                Dao.class.getClassLoader(),        // 代理类加载器
                Dao.class.getInterfaces(),         // 代理的接口
                new DaoJdkProxy(new Dao()));
        dao.select();

    }

运行一下代码,会生成一个匿名的字节码文件,长这样
在这里插入图片描述
也就是说采用jdkProxy获取到的代理对象就是它,执行jdk代理对象的方法就是执行该匿名类的方法,而该方法里具体执行h.invoke()即invocationhandler方法,该方法就是创建代理对象时传入的方法,调用到业务代码实际上是在invocationhandler的invoke()方法里通过反射进行调用
所以,在执行jdk代理下该接口的select方法只会增强一次,而不会传递增强到select()方法里调用的insert()方法。
而cglib动态代理则可以增强两次

  1. cglib
    代理逻辑如下在这里插入图片描述
public class Dao$$EnhancerByCGLIB$$ef1ad419 extends Dao implements Factory {

生成的一个类继承Dao类
在这里插入图片描述

//部分代码如下
public final void insert() {
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
            var10000.intercept(this, CGLIB$insert$0$Method, CGLIB$emptyArgs, CGLIB$insert$0$Proxy);
        } else {
            super.insert();
        }
    }

    final void CGLIB$select$1() {
        super.select();
    }

    public final void select() {
    //cglib代理对象执行select方法会调用到这里
    //获得methodInterceptor和invocationhandler类似都是传入进来的
        MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
        if (var10000 == null) {
            CGLIB$BIND_CALLBACKS(this);
            var10000 = this.CGLIB$CALLBACK_0;
        }

        if (var10000 != null) {
        //执行Intercept方法 即自己传入的内部类的方法
            var10000.intercept(this, CGLIB$select$1$Method, CGLIB$emptyArgs, CGLIB$select$1$Proxy);
        } else {
            super.select();
        }
    }
    enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
                System.out.println("begin intercept");
                //invokeSuper方法调用目标类的方法
                proxy.invokeSuper(o, objects);
       
                System.out.println("end intercept");
                return o;
            }
        });
上述的proxy.invokesuper()方法会调到生成的一个fastclass类的该方法。以一种路由的模式,执行代理对象所执行的方法,也就是说代理对象执行的一个方法里嵌套了另外一个可以代理的方法,则两个方法均会被代理。
 public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
        Dao var10000 = (Dao)var2;
        int var10001 = var1;

        try {
            switch(var10001) {
            case 0:
                var10000.delete();
                return null;
            case 1:
                var10000.insert();
                return null;
            case 2:
                var10000.select();
                return null;
            case 3:
                var10000.wait();
                return null;
            case 4:
                var10000.wait(((Number)var3[0]).longValue(), ((Number)var3[1]).intValue());
                return null;
            case 5:
                var10000.wait(((Number)var3[0]).longValue());
                return null;

总结:
jdkproxy会创建一个代理对象类的文件,而cglib会创建多个文件组合使用,例如代理对象类和一个fastclass路由。所以jdkproxy会比cglib效率高,但是生成代理对象,其实是在spring启动的时候就创建了,所以性能影响不大。而且,jdkproxy和cglib执行代理方法的方式不一样,jdkproxy执行代理的方法其实是在我们写的invocationhandler里通过反射方式来选择性执行或不执行,而cglib可以不通过反射方式执行,而是通过直接调用的方法,通过fastclass路由,直接调用。所以jdkproxy启动快,调用效率低一点,而cglib启动慢,调用快.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张嘉書

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值