Java的Cglib动态代理实现方式详解

 更新时间:2023年11月27日 09:03:52   作者:ps酷教程  
这篇文章主要介绍了Java的Cglib动态代理实现方式详解,CGLIB是强大的、高性能的代码生成库,被广泛应用于AOP框架,它底层使用ASM来操作字节码生成新的类,为对象引入间接级别,以控制对象的访问,需要的朋友可以参考下

Cglib动态代理

我们先通过一个demo看一下Cglib是如何实现动态代理的。

首先定义个服务类,有两个方法并且其中一个方法用final来修饰。

public class PersonService {
    public PersonService() {
        System.out.println("PersonService构造");
    }
    //该方法不能被子类覆盖
    final public Person getPerson(String code) {
        System.out.println("PersonService:getPerson>>"+code);
        return null;
    }
    public void setPerson() {
        System.out.println("PersonService:setPerson");
    }
}

Cglib是无法代理final修饰的方法的,具体原因我们一会通过源码来分析。

然后,定义一个自定义MethodInterceptor。

public class CglibProxyIntercepter 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;
    }
}

我们看一下intercept方法入参,sub:cglib生成的代理对象,method:被代理对象方法,objects:方法入参,methodProxy:代理方法

最后,我们写个例子调用一下,并将Cglib生成的代理类class文件输出磁盘方便我们反编译查看源码。

public class Test {
    public static void main(String[] args) {        //代理类class文件存入本地磁盘
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(PersonService.class);
        enhancer.setCallback(new CglibProxyIntercepter());
        PersonService proxy= (PersonService)  enhancer.create();        	        
        proxy.setPerson();        
        proxy.getPerson("1");     
    } 
}

我们执行一下会发现getPerson因为加final修饰并没有被代理,下面我们通过源码分析一下。

执行前...
PersonService:setPerson
执行后...
PersonService:getPerson>>1

生成代理类 执行Test测试类可以得到Cglib生成的class文件,一共有三个class文件我们反编译以后逐个说一下他们的作用。

在这里插入图片描述

 PersonService$$EnhancerByCGLIB$$eaaaed75就是cglib生成的代理类,它继承了PersonService类。

public class PersonService$$EnhancerByCGLIB$$eaaaed75
  extends PersonService
  implements Factory
{
  private boolean CGLIB$BOUND;
  private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
  private static final Callback[] CGLIB$STATIC_CALLBACKS;
  private MethodInterceptor CGLIB$CALLBACK_0;//拦截器
  private static final Method CGLIB$setPerson$0$Method;//被代理方法
  private static final MethodProxy CGLIB$setPerson$0$Proxy;//代理方法
  private static final Object[] CGLIB$emptyArgs;
  private static final Method CGLIB$finalize$1$Method;
  private static final MethodProxy CGLIB$finalize$1$Proxy;
  private static final Method CGLIB$equals$2$Method;
  private static final MethodProxy CGLIB$equals$2$Proxy;
  private static final Method CGLIB$toString$3$Method;
  private static final MethodProxy CGLIB$toString$3$Proxy;
  private static final Method CGLIB$hashCode$4$Method;
  private static final MethodProxy CGLIB$hashCode$4$Proxy;
  private static final Method CGLIB$clone$5$Method;
  private static final MethodProxy CGLIB$clone$5$Proxy;
  static void CGLIB$STATICHOOK1()
  {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    Class localClass1 = Class.forName("com.demo.proxy.cglib.PersonService$$EnhancerByCGLIB$$eaaaed75");//代理类
    Class localClass2;//被代理类PersionService
    Method[] tmp95_92 = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (localClass2 = Class.forName("java.lang.Object")).getDeclaredMethods());
    CGLIB$finalize$1$Method = tmp95_92[0];
    CGLIB$finalize$1$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "finalize", "CGLIB$finalize$1");
    Method[] tmp115_95 = tmp95_92;
    CGLIB$equals$2$Method = tmp115_95[1];
    CGLIB$equals$2$Proxy = MethodProxy.create(localClass2, localClass1, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
    Method[] tmp135_115 = tmp115_95;
    CGLIB$toString$3$Method = tmp135_115[2];
    CGLIB$toString$3$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
    Method[] tmp155_135 = tmp135_115;
    CGLIB$hashCode$4$Method = tmp155_135[3];
    CGLIB$hashCode$4$Proxy = MethodProxy.create(localClass2, localClass1, "()I", "hashCode", "CGLIB$hashCode$4");
    Method[] tmp175_155 = tmp155_135;
    CGLIB$clone$5$Method = tmp175_155[4];
    CGLIB$clone$5$Proxy = MethodProxy.create(localClass2, localClass1, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
    tmp175_155;
    Method[] tmp223_220 = ReflectUtils.findMethods(new String[] { "setPerson", "()V" }, (localClass2 = Class.forName("com.demo.proxy.cglib.PersonService")).getDeclaredMethods());
    CGLIB$setPerson$0$Method = tmp223_220[0];
    CGLIB$setPerson$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "setPerson", "CGLIB$setPerson$0");
    tmp223_220;
    return;
  }

我们通过代理类的源码可以看到,代理类会获得所有在父类继承来的方法,并且会有MethodProxy与之对应,比如 Method CGLIB$setPerson 0 0 0Method、MethodProxy CGLIB$setPerson 0 0 0Proxy;

方法的调用

//代理方法(methodProxy.invokeSuper会调用)   
final void CGLIB$setPerson$0() {
   super.setPerson();
}
//被代理方法(methodProxy.invoke会调用,这就是为什么在拦截器中调用methodProxy.invoke会死循环,一直在调用拦截器)
public final void setPerson() {
   MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
   if(this.CGLIB$CALLBACK_0 == null) {
      CGLIB$BIND_CALLBACKS(this);
      var10000 = this.CGLIB$CALLBACK_0;
   }

   if(var10000 != null) {         //调用拦截器
      var10000.intercept(this, CGLIB$setPerson$0$Method, CGLIB$emptyArgs, CGLIB$setPerson$0$Proxy);
   } else {
      super.setPerson();
   }
}

调用过程:代理对象调用this.setPerson方法->调用拦截器->methodProxy.invokeSuper->CGLIB$setPerson$0->被代理对象setPerson方法

MethodProxy

拦截器MethodInterceptor中就是由MethodProxy的invokeSuper方法调用代理方法的,MethodProxy非常关键,我们分析一下它具体做了什么。

创建MethodProxy

package com.zzhua.cglib.callback;

import net.sf.cglib.core.AbstractClassGenerator;
import net.sf.cglib.core.GeneratorStrategy;
import net.sf.cglib.core.NamingPolicy;

public class MethodProxy {
    private Signature sig1;
    private Signature sig2;
    private MethodProxy.CreateInfo createInfo;
    private final Object initLock = new Object();
    private volatile MethodProxy.FastClassInfo fastClassInfo;

    //c1:被代理对象Class    
    //c2:代理对象Class    
    //desc:入参类型    
    //name1:被代理方法名    
    //name2:代理方法名
    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 MethodProxy.CreateInfo(c1, c2);
        return proxy;
    }

    private static class CreateInfo {
        Class c1;
        Class c2;
        NamingPolicy namingPolicy;
        GeneratorStrategy strategy;
        boolean attemptLoad;

        public CreateInfo(Class c1, Class c2) {
            this.c1 = c1;
            this.c2 = c2;
            AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
            if (fromEnhancer != null) {
                this.namingPolicy = fromEnhancer.getNamingPolicy();
                this.strategy = fromEnhancer.getStrategy();
                this.attemptLoad = fromEnhancer.getAttemptLoad();
            }
        }
    }
}

invokeSuper调用

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

private static class FastClassInfo {
    FastClass f1;//被代理类FastClass
    FastClass f2;//代理类FastClass
    int i1; //被代理类的方法签名(index)
    int i2;//代理类的方法签名

    private FastClassInfo() {
    }
}

上面代码调用过程就是获取到代理类对应的FastClass,并执行了代理方法。还记得之前生成三个class文件吗?

PersonService$$EnhancerByCGLIB$$eaaaed75$$FastClassByCGLIB$$355cb7ea.class就是代理类的FastClass,

PersonService$$FastClassByCGLIB$$a076b035.class就是被代理类的FastClass。

FastClass机制

Cglib动态代理执行代理方法效率之所以比JDK的高是因为Cglib采用了FastClass机制,它的原理简单来说就是:为代理类和被代理类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int类型)。

这个index当做一个入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比JDK动态代理通过反射调用高。

下面我们反编译一个FastClass看看:

//根据方法签名获取index 
public int getIndex(Signature var1) {
    String var10000 = var1.toString();
     switch(var10000.hashCode()) {
     case -2077043409:
        if(var10000.equals("getPerson(Ljava/lang/String;)Lcom/demo/pojo/Person;")) {
           return 21;
        }
        break;
     case -2055565910:
        if(var10000.equals("CGLIB$SET_THREAD_CALLBACKS([Lnet/sf/cglib/proxy/Callback;)V")) {
           return 12;
        }
        break;
     case -1902447170:
        if(var10000.equals("setPerson()V")) {
           return 7;
        }
        break;
  //省略部分代码.....  

//根据index直接定位执行方法
public Object invoke(int var1, Object var2, Object[] var3) throws InvocationTargetException {
     eaaaed75 var10000 = (eaaaed75)var2;
     int var10001 = var1;

     try {
        switch(var10001) {
        case 0:
           return new Boolean(var10000.equals(var3[0]));
        case 1:
           return var10000.toString();
        case 2:
           return new Integer(var10000.hashCode());
        case 3:
           return var10000.newInstance((Class[])var3[0], (Object[])var3[1], (Callback[])var3[2]);
        case 4:
           return var10000.newInstance((Callback)var3[0]);
        case 5:
           return var10000.newInstance((Callback[])var3[0]);
        case 6:
           var10000.setCallback(((Number)var3[0]).intValue(), (Callback)var3[1]);
           return null;
        case 7:
           var10000.setPerson();
           return null;
        case 8:
           var10000.setCallbacks((Callback[])var3[0]);
           return null;
        case 9:
           return var10000.getCallback(((Number)var3[0]).intValue());
        case 10:
           return var10000.getCallbacks();
        case 11:
           eaaaed75.CGLIB$SET_STATIC_CALLBACKS((Callback[])var3[0]);
           return null;
        case 12:
           eaaaed75.CGLIB$SET_THREAD_CALLBACKS((Callback[])var3[0]);
           return null;
        case 13:
           return eaaaed75.CGLIB$findMethodProxy((Signature)var3[0]);
        case 14:
           return var10000.CGLIB$toString$3();
        case 15:
           return new Boolean(var10000.CGLIB$equals$2(var3[0]));
        case 16:
           return var10000.CGLIB$clone$5();
        case 17:
           return new Integer(var10000.CGLIB$hashCode$4());
        case 18:
           var10000.CGLIB$finalize$1();
           return null;
        case 19:
           var10000.CGLIB$setPerson$0();
           return null;
       //省略部分代码....
     } catch (Throwable var4) {
        throw new InvocationTargetException(var4);
     }

     throw new IllegalArgumentException("Cannot find matching method/constructor");
  }

FastClass并不是跟代理类一块生成的,而是在第一次执行MethodProxy invoke/invokeSuper时生成的并放在了缓存中。

//MethodProxy invoke/invokeSuper都调用了init()
private void init() {
    if(this.fastClassInfo == null) {
        Object var1 = this.initLock;
        synchronized(this.initLock) {
            if(this.fastClassInfo == null) {
                MethodProxy.CreateInfo ci = this.createInfo;
                MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                fci.f1 = helper(ci, ci.c1);//如果缓存中就取出,没有就生成新的FastClass
                fci.f2 = helper(ci, ci.c2);
                fci.i1 = fci.f1.getIndex(this.sig1);//获取方法的index
                fci.i2 = fci.f2.getIndex(this.sig2);
                this.fastClassInfo = fci;
                this.createInfo = null;
            }
        }
    }
}

 至此,Cglib动态代理的原理我们就基本搞清楚了,代码细节有兴趣可以再研究下。

最后我们总结一下JDK动态代理和Gglib动态代理的区别:

1.JDK动态代理是实现了被代理对象的接口,Cglib是继承了被代理对象。

2.JDK和Cglib都是在运行期生成字节码,JDK是直接写Class字节码,Cglib使用ASM框架写Class字节码,Cglib代理实现更复杂,生成代理类比JDK效率低。

3.JDK调用代理方法,是通过反射机制调用,Cglib是通过FastClass机制直接调用方法,Cglib执行效率更高。

到此这篇关于Java的Cglib动态代理实现方式详解的文章就介绍到这了,更多相关Cglib动态代理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • jvm堆外内存排查图文举例详解

    jvm堆外内存排查图文举例详解

    Java应用程序通过直接方式从操作系统中申请的内存,叫堆外内存,这篇文章主要给大家介绍了关于jvm堆外内存排查的相关资料,文中通过代码介绍的非常详细,需要的朋友可以参考下
    2023-12-12
  • SpringBoot获取HttpServletRequest的3种方式总结

    SpringBoot获取HttpServletRequest的3种方式总结

    这篇文章主要给大家介绍了关于SpringBoot获取HttpServletRequest的3种方式,在Spring boot项目中经常要用到Servlet的常用对象如HttpServletRequest request,HttpServletResponse response,HttpSession session,需要的朋友可以参考下
    2023-08-08
  • java开发SpringBoot参数校验过程示例教程

    java开发SpringBoot参数校验过程示例教程

    这篇文章主要为大家介绍了SpringBoot如何进行参数校验的过程示例详解教程,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2021-10-10
  • Java值得使用Lambda的8个场景合集

    Java值得使用Lambda的8个场景合集

    可能对不少人来说,Lambda显得陌生又复杂,觉得Lambda会导致代码可读性下降,但毕竟2023年了,JDK都出了那么多新版本,是时候试试Lambda了
    2023-08-08
  • Spring Boot Rest常用框架注解详情简介

    Spring Boot Rest常用框架注解详情简介

    这篇文章主要介绍了Spring Boot Rest常用框架注解,通过将尝试解释Spring Boot Rest API的不同注释,这些注释是Spring Boot中REST API所必需的,需要的朋友可以参考一下
    2022-06-06
  • java中Iterator和ListIterator实例详解

    java中Iterator和ListIterator实例详解

    这篇文章主要介绍了java中Iterator和ListIterator实例详解,具有一定借鉴价值,需要的朋友可以参考下。
    2017-12-12
  • Springboot 如何设置启动内存

    Springboot 如何设置启动内存

    这篇文章主要介绍了Springboot 如何设置启动内存,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-02-02
  • 详解SpringCloud Gateway 2020.0.2最新版

    详解SpringCloud Gateway 2020.0.2最新版

    这篇文章主要介绍了SpringCloud Gateway 2020.0.2最新版,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-04-04
  • springboot json时间格式化处理的方法

    springboot json时间格式化处理的方法

    这篇文章主要介绍了springboot json时间格式化处理的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • Java泛型映射不同的值类型详解及实例代码

    Java泛型映射不同的值类型详解及实例代码

    这篇文章主要介绍了Java泛型映射不同的值类型详解及实例代码的相关资料,需要的朋友可以参考下
    2017-02-02

最新评论