cglib动态代理底层实现分析[ java ]

weblog 精帖
1820

前言

        好久就开始关注cglib动态代理了,但是到今天才算是有点搞明白~cglib动态代理底层用到了asm等字节码操作框架。不懂的可以先百度百度asm是干啥的,在这里也提供一些文章,但是可能比较深入:http://www.jiajiajia.club/search?str=asm

jdk动态代理的原理 http://www.jiajiajia.club/weblog/blog/artical/60

cglib动态代理helloword http://www.jiajiajia.club/blog/artical/134

什么是cglib?

        cglib是一个强大的,高性能,高质量的Code生成类库,它可以在运行期扩展Java类与实现Java接口。Hibernate支持它来实现PO(Persistent Object 持久化对象)字节码的动态生成。CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。除了CGLIB包,脚本语言例如Groovy和BeanShell,也是使用ASM来生成java的字节码。当然不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉。

---百度百科。

如何使用cglib创建一个代理类?

需要依赖cglib的jar包,Pom文件如下:

<dependency>
	<groupId>cglib</groupId>
	<artifactId>cglib</artifactId>
	<version>2.2.2</version>
</dependency>

helloword代码:

package cglib;
 
import java.lang.reflect.Method;
 
import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
 
/**
 * *受理类
 * @author Administrator
 *
 */
class Target{
    public void f(){
        System.out.println("Target f()");
    }
    public void g(){
        System.out.println("Target g()");
    }
}
/**
 * *方法拦截器
 * @author Administrator
 *
 */
class Interceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args,MethodProxy proxy) throws Throwable {
        System.out.println("before");//目标方法执行前
        proxy.invokeSuper(obj, args);//调用目标类的方法
        System.out.println("after");//目标方法执行后
        return null;
    }
}
 
/**
 * 测试
 * @author Administrator
 */
public class MainTest {
  public static void main(String[] args) {
     System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\test\\cglib");
       Enhancer eh = new Enhancer();//实例化一个增强器,也就是cglib中的一个class generator
       eh.setSuperclass(Target.class);//设置目标类
       eh.setCallback(new Interceptor());// 设置拦截对象
       Target t = (Target) eh.create();// 生成代理类并返回一个实例
       t.f();
       t.g();
       System.out.println("t对象的父类:"+t.getClass().getSuperclass().getName());
  }
}

cglib底层分析

在上述案例中已经把生成的代理类的类文件储存在E:\\test\\cglib中

在文件夹中会发现生成的三个class文件,下面将一一叙述其作用。

先看Target$$EnhancerByCGLIB$$fd8b7134这个类,反编译这个class文件,其中贴出重要代码:

public class Target$$EnhancerByCGLIB$$fd8b7134 extends Target 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$f$0$Method;
    private static final MethodProxy CGLIB$f$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$g$1$Method;
    private static final MethodProxy CGLIB$g$1$Proxy;
    private static final Method CGLIB$finalize$2$Method;
    private static final MethodProxy CGLIB$finalize$2$Proxy;
    private static final Method CGLIB$equals$3$Method;
    private static final MethodProxy CGLIB$equals$3$Proxy;
    private static final Method CGLIB$toString$4$Method;
    private static final MethodProxy CGLIB$toString$4$Proxy;
    private static final Method CGLIB$hashCode$5$Method;
    private static final MethodProxy CGLIB$hashCode$5$Proxy;
    private static final Method CGLIB$clone$6$Method;
    private static final MethodProxy CGLIB$clone$6$Proxy;
    
    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        final Class<?> forName = Class.forName("test.demo.cglib.Target$$EnhancerByCGLIB$$fd8b7134");
        final Class<?> forName2;
        final Method[] methods = ReflectUtils.findMethods(new String[] { "finalize", "()V", "equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;" }, (forName2 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$finalize$2$Method = methods[0];
        CGLIB$finalize$2$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()V", "finalize", "CGLIB$finalize$2");
        CGLIB$equals$3$Method = methods[1];
        CGLIB$equals$3$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$3");
        CGLIB$toString$4$Method = methods[2];
        CGLIB$toString$4$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()Ljava/lang/String;", "toString", "CGLIB$toString$4");
        CGLIB$hashCode$5$Method = methods[3];
        CGLIB$hashCode$5$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()I", "hashCode", "CGLIB$hashCode$5");
        CGLIB$clone$6$Method = methods[4];
        CGLIB$clone$6$Proxy = MethodProxy.create((Class)forName2, (Class)forName, "()Ljava/lang/Object;", "clone", "CGLIB$clone$6");
        final Class<?> forName3;
        final Method[] methods2 = ReflectUtils.findMethods(new String[] { "f", "()V", "g", "()V" }, (forName3 = Class.forName("test.demo.cglib.Target")).getDeclaredMethods());
        CGLIB$f$0$Method = methods2[0];
        CGLIB$f$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "f", "CGLIB$f$0");
        CGLIB$g$1$Method = methods2[1];
        CGLIB$g$1$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "g", "CGLIB$g$1");
    }
    
    final void CGLIB$f$0() {
        super.f();
    }
    
    public final void f() {
        MethodInterceptor cglib$CALLBACK_2;
        MethodInterceptor cglib$CALLBACK_0;
        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
        }
        if (cglib$CALLBACK_0 != null) {
            cglib$CALLBACK_2.intercept((Object)this, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Method, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$emptyArgs, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Proxy);
            return;
        }
        super.f();
    }
    
    final void CGLIB$g$1() {
        super.g();
    }
    
    public final void g() {
        MethodInterceptor cglib$CALLBACK_2;
        MethodInterceptor cglib$CALLBACK_0;
        if ((cglib$CALLBACK_0 = (cglib$CALLBACK_2 = this.CGLIB$CALLBACK_0)) == null) {
            CGLIB$BIND_CALLBACKS(this);
            cglib$CALLBACK_2 = (cglib$CALLBACK_0 = this.CGLIB$CALLBACK_0);
        }
        if (cglib$CALLBACK_0 != null) {
            cglib$CALLBACK_2.intercept((Object)this, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$g$1$Method, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$emptyArgs, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$g$1$Proxy);
            return;
        }
        super.g();
}
}

        从代码中可以看出Target$$EnhancerByCGLIB$$fd8b7134这个代理类继承了我们声明的Target类,(注意区别jdk动态代理,jdk动态代理是实现一个接口)。并且以f方法为例,代理类除了生成了重写了Target类的f()方法,还生成了一个CGLIB$f$0()方法,CGLIB$f$0()的方法体中就调用了一下父类的f()方法(先提一下,其实CGLIB$f$0()方法就是我们需要调用目标方法),另外我们在代码中调用f()方法的时候其实调用的就是代理类重写了父类的f()方法。

        在Target$$EnhancerByCGLIB$$fd8b7134类的属性中可以看到有个MethodInterceptor属性。在代理对象中MethodInterceptor对象引用的就是我们代码中定义的并且实现MethodInterceptor接口的Interceptor类的对象。那么代理类是怎么引用到的呢?

Enhancer eh = new Enhancer();//实例化一个增强器,也就是cglib中的一个class generator
eh.setSuperclass(Target.class);//设置目标类
eh.setCallback(new Interceptor());// 设置拦截对象

        看看main函数的代码就知道了,它是在生成代理对象之前调用setCallback方法传入的。此时代理对象就和MethodInterceptor扯上了关系。
        之前说了,代码中调用的f()方法,就是在调用代理对象中的f()方法,在代理对象的f()中可以清楚的看到一行代码:

cglib$CALLBACK_2.intercept((Object)this, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Method, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$emptyArgs, Target$$EnhancerByCGLIB$$fd8b7134.CGLIB$f$0$Proxy);

        它在干什么?没错,他就在调用我们定义的方法拦截器对象(Interceptor)的intercept方法。也就是说在调用代理对象的f方法的时候,首先就调用了自定义的方法拦截器。在方法拦截其中proxy.invokeSuper(obj, args);这段代码就是在调用目标方法,也即CGLIB$f$0()方法。

        从在主函数代码中调用f()方法到调用方法拦截器都很好理解,但是到了调目标方法CGLIB$f$0()就有点绕了。其实它内部是通过Fastclass 机制来实现的。

Fastclass 机制分析

        在jkd动态代理中,调用目标方法是通过反射来实现的。而通过反射调用效率是比较低的。所以cglib采用了FastClass的机制来实现对被拦截方法的调用,这也是和jdk代理的一个较大的区别。FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法。这也是cglib代理效率较jkd代理高的重要原因之一。
举个栗子:

package test.demo.cglib.box;
public class TestFastClass {
    public static void main(String[] args){
    	Target tt = new Target();
    	FastClass fc = new FastClass();
        int index = fc.getIndex("f()V");
        fc.invoke(index, tt, null);
    }
}
/**
 *	目标类
 */
class Target{
    public void f(){
        System.out.println("f method");
    }
    
    public void g(){
        System.out.println("g method");
    }
}
class FastClass{
	/**
	 * 调用目标方法
	 */
    public Object invoke(int index, Object obj, Object[] ol){
    	Target t = (Target) obj;
        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;
    }
}

        上述案例应该很好理解,getIndex()方法中通过不同的方法描述,产生不同的索引值。然后通过索引值到invoke()方法中调取目标方法。

MethodProxy类

        MethodProxy是很关键的一个类,也比较看懂。在方法拦截器中就是调用的MethodProxy类对象的invokeSuper方法,通过fastClass机制调用的目标对象。
        MethodProxy对象的创建是在创建代理类的时候创建的,对象的信息包含了目标类的类对象、代理类的类对象、方法描述、目标方法的方法名以及代理方法的方法名。其目的就是为了创建FastClass对象。还是以f方法为例:

private static final Method CGLIB$f$0$Method;
private static final MethodProxy CGLIB$f$0$Proxy;
static void CGLIB$STATICHOOK1() {
	    CGLIB$f$0$Method = methods2[0];
        CGLIB$f$0$Proxy = MethodProxy.create((Class)forName3, (Class)forName, "()V", "f", "CGLIB$f$0");
}

FastClass是什么时候创建的?

走进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();
        }
}

在进入init();方法

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;
                }
            }
        }
}

        fastClassInfo就是在init()方法中创建的。并不是和代理类一块生成的。并且是第一次调用时创建,并把它放入了缓存中。看看这个FastClassInfo里面都有什么东西。

        接下来反编译Target$$FastClassByCGLIB$$a7520c6d.class,注意为了方便下方好多switch case中的代码被我删了,其实还有equals方法等的索引。

package test.demo.cglib;
import net.sf.cglib.reflect.*;
import net.sf.cglib.core.*;
import java.lang.reflect.*;

public class Target$$FastClassByCGLIB$$a7520c6d extends FastClass{
    public Target$$FastClassByCGLIB$$a7520c6d(final Class clazz) {
        super(clazz);
    }
    public int getIndex(final Signature signature) {
        final String string = signature.toString();
        switch (string.hashCode()) {
           
            case 3078479: {
                if (string.equals("f()V")) {
                    return 0;
                }
                break;
            }
            case 3108270: {
                if (string.equals("g()V")) {
                    return 1;
                }
                break;
            }
        }
        return -1;
    }
    public Object invoke(final int n, final Object o, final Object[] array) throws InvocationTargetException {
        final Target target = (Target)o;
        try {
            switch (n) {
                case 0: {
                    target.f();
                    return null;
                }
                case 1: {
                    target.g();
                    return null;
                }
            }
        }
        catch (Throwable t) {
            throw new InvocationTargetException(t);
        }
        throw new IllegalArgumentException("Cannot find matching method/constructor");
    }
}

        这就是生成后的FastClass的代码。通过getIndex()方法根据目标方法签名获取方法的索引,通过invoke()方法根据方法索引调用目标方法。

猜你喜欢