代理模式-CGLib动态代理

动态代理模式的核心就是“字节码重组”。
满足代理模式的三个条件:
1.执行者和被代理人
2.对于被代理人来说,这件事情一定要做,但是自己不想做,没时间去做或者自己做不到,需要找代理
3.需要获取被代理人的个人信息

cglib 动态代理示例

cglib生成动态代理类的机制——通过类继承
JDK中提供的生成动态代理类的机制有个鲜明的特点:某个类必须有实现的接口,而生成的代理类也只能代理某个类接口定义的方法。例如:例子有个XiaoMing的类实现了Person接口外,另外实现了getName()方法,从而在产生动态代理类中不会有这个方法了;更极端的情况就是某个类没有实现接口,那么这个类就不能使用JDK产生动态代理了。

正好有cglib。CGLIB(Code Generation Library),是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。

cglib创建某个类A的动态代理的模式为:
1.查找A上的所有非final的public类型定义的方法;
2.将这些方法的定义转换成字节码;
3.将组成的字节码转换成相应的代理class对象;
4.实现MothodIterceptor接口,用来处理对代理类上所有方法的请求(这个接口和JDK动态代理InvocationHandler的功能和角色一样)。

报错如下,需要引用asm.jar:

Exception in thread "main" java.lang.NoClassDefFoundError: org/objectweb/asm/Type
    at net.sf.cglib.core.TypeUtils.parseType(TypeUtils.java:184)
    at net.sf.cglib.core.KeyFactory.<clinit>(KeyFactory.java:66)
    at net.sf.cglib.proxy.Enhancer.<clinit>(Enhancer.java:69)
    at fn.proxy.cglib.Meipo.getInstance(Meipo.java:12)
    at fn.proxy.cglib.Test.main(Test.java:5)
Caused by: java.lang.ClassNotFoundException: org.objectweb.asm.Type
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.net.URLClassLoader$1.run(Unknown Source)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
    at java.lang.ClassLoader.loadClass(Unknown Source)
    ... 5 more

又来一个报错如下:

class net.sf.cglib.core.DebuggingClassWriter has interface org.objectweb.asm.ClassVisitor as super class

cglib版本为3.0或3.1,org.objectweb.asm版本为3.1.0时,版本冲突,报错java.lang.IncompatibleClassChangeError: class,使用cglib 2.2 可解决此问题,该版本中的DebuggingClassWriter的父类为ClassWriter。


1.创建被代理XiaoMing类

public class XiaoMing {

    /**
     * 寻找真爱
     */
    public void findLove() {
        System.out.println("寻找白富美!!!");
    }
}

2.创建Meipo类,实现MethodInterceptor接口

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class Meipo implements MethodInterceptor{

    public Object getInstance(Class clazz) {
        Enhancer enhancer = new Enhancer();
        //将父类赋给了谁?
        //这里告诉CGLib,生成的子类需要继承的哪个类
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);

        //第一步,生成源代码
        //第二步,编辑成class文件
        //第三步,加载到JVM中,并返回被代理对象
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        // TODO Auto-generated method stub

        System.out.println("我是媒婆...");
        System.out.println("开始海选...");
        //proxy.invoke(obj, args);
        /**
         * 这里的obj的引用是由CGLib给我们new出来的
         * CGLib new出来以后的对象,是被代理对象的子类(继承了我们自己的类)
         * OOP在new子类之前,实际上默认先调用了super()方法
         * new子类的同时,必须先new父类,这就相当于是间接的持有了我们父类的引用
         * 子类重写了父类的所有方法
         * 我们改变了子类对象的某些属性,是可以间接操作父类的属性
         */
        proxy.invokeSuper(obj, args);
        System.out.println("结束海选...");
        return null;
    }

}

与JDK动态代理相比,cglib可以实现对一般类的代理而无需实现接口。在上例中通过下列步骤来生成目标类Target的代理类:

  1. 创建Enhancer实例
  2. 通过setSuperclass方法来设置目标类
  3. 通过setCallback 方法来设置拦截对象
  4. create方法生成XiaoMing的代理类,并返回代理类的实例

3.测试Test类代码如下:

import net.sf.cglib.core.DebuggingClassWriter;

public class Test {
    public static void main(String[] args) {
        //让我们看看通过cglib生成的class文件内容
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "F:/fn");
        //JDK的动态代理是通过接口来进行强制转换
        //生成以后的代理对象,可以强制转换为接口

        //CGLib的动态dialing是通过生成一个被代理对象的子类,然后重写父类的方法
        //生成以后的对象,可以强制转换为被代理对象
        //子类引用赋值给父类
        XiaoMing obj = (XiaoMing) new Meipo().getInstance(XiaoMing.class);
        //System.out.println("对象是"+new Meipo().getInstance(XiaoMing.class).getClass());
        System.out.println("对象是"+obj.getClass());
        obj.findLove();
    }
}

运行执行结果如下:

CGLIB debugging enabled, writing to 'F:/fn'
对象是class fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$f3061164
我是媒婆...
开始海选...
寻找白富美!!!
结束海选...

代理类分析

在示例代码中我们通过设置DebuggingClassWriter.DEBUG_LOCATION_PROPERTY的属性值来获取cglib生成的代理类。通过之前分析的命名规则我们可以很容易的在F:/fn下面找到生成的代理类 fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$f3061164

使用jd-gui进行反编译(由于版本的问题,此处只能显示部分代码,可以结合javap的反编译结果来进行分析),由于cglib会代理Object中的finalize,equals, toString,hashCode,clone方法,为了清晰的展示代理类我们省略这部分代码,反编译的结果如下:


public class XiaoMing$$EnhancerByCGLIB$$5a444703
  extends XiaoMing
  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$findLove$0$Method;
  private static final MethodProxy CGLIB$findLove$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("fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$5a444703");
    Class localClass2;
    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[] { "findLove", "()V" }, (localClass2 = Class.forName("fn.proxy.cglib.XiaoMing")).getDeclaredMethods());
    CGLIB$findLove$0$Method = tmp223_220[0];
    CGLIB$findLove$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "findLove", "CGLIB$findLove$0");
    tmp223_220;
    return;
  }

  final void CGLIB$findLove$0()
  {
    super.findLove();
  }

  public final void findLove()
  {
    MethodInterceptor tmp4_1 = this.CGLIB$CALLBACK_0;
    if (tmp4_1 == null)
    {
      tmp4_1;
      CGLIB$BIND_CALLBACKS(this);
    }
    if (this.CGLIB$CALLBACK_0 != null) {
      return;
    }
    super.findLove();
  }
}

代理类(XiaoMing$$EnhancerByCGLIB$$5a444703)继承了目标类(XiaoMing),至于代理类实现的Factory接口与本文无关,残忍无视。代理类为每个目标类的方法生成两个方法,例如针对目标类中的每个非private方法,代理类会生成两个方法,以findLove方法为例:一个是@Override的方法,一个是CGLIB$findLove$0CGLIB$findLove$0相当于目标类的findLove方法)。。我们在示例代码中调用目标类的方法obj.findLove()时,实际上调用的是代理类中的findLove()方法。接下来我们着重分析代理类中的findLove方法,看看是怎么实现的代理功能。

当调用代理类的findLove方法时,先判断是否已经存在实现了MethodInterceptor接口的拦截对象,如果没有的话就调用CGLIB$BIND_CALLBACKS方法来获取拦截对象,CGLIB$BIND_CALLBACKS的反编译结果如下:

  private static final void CGLIB$BIND_CALLBACKS(Object paramObject)
  {
    5a444703 local5a444703 = (5a444703)paramObject;
    if (!local5a444703.CGLIB$BOUND)
    {
      local5a444703.CGLIB$BOUND = true;
      Object tmp23_20 = CGLIB$THREAD_CALLBACKS.get();
      if (tmp23_20 == null)
      {
        tmp23_20;
        CGLIB$STATIC_CALLBACKS;
      }
      local5a444703.CGLIB$CALLBACK_0 = (tmp31_28 == null ? tmp31_28 : (MethodInterceptor)((Callback[])tmp23_20)[0]);
    }
  }

CGLIB$BIND_CALLBACKS 先从CGLIB$THREAD_CALLBACKS中get拦截对象,如果获取不到的话,再从CGLIB$STATIC_CALLBACKS来获取,如果也没有则认为该方法不需要代理。

那么拦截对象是如何设置到CGLIB$THREAD_CALLBACKS 或者 CGLIB$STATIC_CALLBACKS中的呢?

在Jdk动态代理中拦截对象是在实例化代理类时由构造函数传入的,在cglib中是调用Enhancer的create方法来生成代理类实例并设置拦截对象的。create的调用轨迹为:

 1. Enhancer:AbstractClassGenerator.create      
 2. Enhancer:firstInstance
 3. Enhancer:createUsingReflection      
 4. Enhancer:setThreadCallbacks
 5. Enhancer:setCallbacksHelper
 6. Enhancer:CGLIB$SET_THREAD_CALLBACKS

在第6步,调用了代理类的CGLIB$SET_THREAD_CALLBACKS来完成拦截对象的注入。下面我们看一下CGLIB$SET_THREAD_CALLBACKS的反编译结果:

  public static void CGLIB$SET_THREAD_CALLBACKS(Callback[] paramArrayOfCallback)
  {
    CGLIB$THREAD_CALLBACKS.set(paramArrayOfCallback);
  }

CGLIB$SET_THREAD_CALLBACKS方法中调用了CGLIB$THREAD_CALLBACKS的set方法来保存拦截对象,在CGLIB$BIND_CALLBACKS方法中使用了CGLIB$THREAD_CALLBACKS的get方法来获取拦截对象,并保存到CGLIB$CALLBACK_0中。
这样,在我们调用代理类的findLove方法时,就可以获取到我们设置的拦截对象,然后通过 tmp4_1.intercept(this, CGLIB$findLove$0$Method, CGLIB$emptyArgs, CGLIB$findLove$0$Proxy) 来实现代理。这里来解释一下intercept方法的参数含义:

@para1 obj :代理对象本身

@para2 method : 被拦截的方法对象

@para3 args:方法调用入参

@para4 proxy:用于调用被拦截方法的方法代理对象

这里会有一个疑问,为什么不直接反射调用代理类生成的(CGLIB$findLove$0)来间接调用目标类的被拦截方法,而使用proxy的invokeSuper方法呢?这里就涉及到了另外一个点— FastClass 。

Fastclass 机制分析

JDK动态代理的拦截对象是通过反射的机制来调用被拦截方法的,反射的效率比较低,所以cglib采用了FastClass的机制来实现对被拦截方法的调用。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法,下面用一个小例子来说明一下,这样比较直观:

public class test10 {
    public static void main(String[] args){
        Test tt = new Test();
        Test2 fc = new Test2();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}

class Test{
    public void f(){
        System.out.println("f method");
    }

    public void g(){
        System.out.println("g method");
    }
}
class Test2{
    public Object invoke(int index, Object o, Object[] ol){
        Test t = (Test) o;
        switch(index){
        case 1:
            t.f();
            return null;
        case 2:
            t.g();
            return null;
        }
        return null;
    }

    public int getIndex(String signature){
        switch(signature.hashCode()){
        case 3078479:
            return 1;
        case 3108270:
            return 2;
        }
        return -1;
    }
}

上例中,Test2是Test的Fastclass,在Test2中有两个方法getIndex和invoke。在getIndex方法中对Test的每个方法建立索引,并根据入参(方法名+方法的描述符)来返回相应的索引。Invoke根据指定的索引,以ol为入参调用对象O的方法。这样就避免了反射调用,提高了效率。代理类(XiaoMing$$EnhancerByCGLIB$$5a444703)中与生成Fastclass相关的代码如下:

Class localClass1 = Class.forName("fn.proxy.cglib.XiaoMing$$EnhancerByCGLIB$$5a444703");
localClass2 = Class.forName("fn.proxy.cglib.XiaoMing");
CGLIB$findLove$0$Proxy = MethodProxy.create(localClass2, localClass1, "()V", "findLove", "CGLIB$findLove$0");

MethodProxy中会对localClass1和localClass2进行分析并生成FastClass,然后再使用getIndex来获取方法g 和 CGLIB$findLove$0的索引,具体的生成过程将在后续进行介绍,这里介绍一个关键的内部类:

 private static class FastClassInfo
    {
        FastClass f1; // fn.proxy.cglib.XiaoMing的fastclass
        FastClass f2; // XiaoMing$$EnhancerByCGLIB$$5a444703 的fastclass
        int i1; //方法findLove在f1中的索引
        int i2; //方法CGLIB$findLove$0在f2中的索引
    }

MethodProxy 中invokeSuper方法的代码如下:

    FastClassInfo fci = fastClassInfo;
    return fci.f2.invoke(fci.i2, obj, args);

当调用invokeSuper方法时,实际上是调用代理类的CGLIB$findLove$0方法,CGLIB$findLove$0直接调用了目标类的findLove方法。所以,在第一节示例代码中我们使用invokeSuper方法来调用被拦截的目标类方法。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值