当前位置: 首页 > 工具软件 > Intercept > 使用案例 >

CGLIB动态代理之intercept函数刨析

上官景铄
2023-12-01

网上搜CGLIB动态代理,几乎所有的博文都只给了示例代码而缺少对代码的解释说明(特别是关键的intercept函数),看完实在是云里雾里。所以,这篇博文将带你从源码的角度来理解intercept函数。

前言

关于如何使用CGLIB创建动态代理,网上已经有很多资料,这里就不再赘述。本文将使用如下代码进行分析,如果你还看不懂下面的代码,请先自行搜索资料看懂后再继续后面的内容。

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;


class Human {
    public void say() {
        System.out.println("I am super man~~~");
    }
}

class ProxyWithCglib {
    public static Object newProxy(Object object) {
        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(object.getClass());

        MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
        myMethodInterceptor.bind(object);
        enhancer.setCallback(myMethodInterceptor);

        return enhancer.create();
    }
}

class MyMethodInterceptor implements MethodInterceptor {

    private Object target;

    public void bind(Object o) {
        this.target = o;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("pre-function logic.");
        
        Object ret = methodProxy.invokeSuper(o, objects);

        System.out.println("post-function logic.");
        
        return ret;
    }
}

public class Test {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./debug_info"); // 用于输出CGLIB产生的类
        Human humanProxy = (Human) ProxyWithCglib.newProxy(new Human());
        System.out.println(humanProxy.getClass());
        humanProxy.say();
    }
}

简要对代码进行一些说明:

  • Human类为被代理对象;
  • MyMethodInterceptor中的target属性用于保存被代理对象的实例;
  • ProxyWithCglib类的静态方法newProxy用于创建代理对象实例;
  • 运行代码即可在debug_info目录下看到运行时生成的类,这将用于我们后续的分析

理解intercept的四个参数

要理解intercept方法的参数的含义,一个不错的办法就是看看运行时调用intercept方法的时候传入的参数是什么。

为了找到在哪里调用了intercept方法,我们从main方法的最后一行:

humanProxy.say();

开始跟。

根据

System.out.println(humanProxy.getClass());

这一行我们可以知道运行时生成的类为:

Human$$EnhancerByCGLIB$$xxxxxx

这个类的代码为(为了便于阅读,只放出我们关注的部分):

public class Human$$EnhancerByCGLIB$$1a29a813 extends Human implements Factory {
    private boolean CGLIB$BOUND;
    public static Object CGLIB$FACTORY_DATA;
    private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
    private static final Callback[] CGLIB$STATIC_CALLBACKS;
    private MethodInterceptor CGLIB$CALLBACK_0;
    private static Object CGLIB$CALLBACK_FILTER;
    private static final Method CGLIB$say$0$Method;
    private static final MethodProxy CGLIB$say$0$Proxy;
    private static final Object[] CGLIB$emptyArgs;
    private static final Method CGLIB$equals$1$Method;
    private static final MethodProxy CGLIB$equals$1$Proxy;
    private static final Method CGLIB$toString$2$Method;
    private static final MethodProxy CGLIB$toString$2$Proxy;
    private static final Method CGLIB$hashCode$3$Method;
    private static final MethodProxy CGLIB$hashCode$3$Proxy;
    private static final Method CGLIB$clone$4$Method;
    private static final MethodProxy CGLIB$clone$4$Proxy;

    static void CGLIB$STATICHOOK1() {
        CGLIB$THREAD_CALLBACKS = new ThreadLocal();
        CGLIB$emptyArgs = new Object[0];
        Class var0 = Class.forName("Human$$EnhancerByCGLIB$$1a29a813");
        Class var1;
        Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
        CGLIB$say$0$Method = ReflectUtils.findMethods(new String[]{"say", "()V"}, (var1 = Class.forName("Human")).getDeclaredMethods())[0];
        CGLIB$say$0$Proxy = MethodProxy.create(var1, var0, "()V", "say", "CGLIB$say$0");
        // .....
    }

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

    public final void say() {
        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$say$0$Method, CGLIB$emptyArgs, CGLIB$say$0$Proxy);
        } else {
            super.say();
        }
    }
	//.....
}

通过这段代码,我们可以获得如下信息:

  • CGLIB为我们生成的代理类继承了Human,验证了CGLIB是基于继承来实现代理的。

  • 当我们调用humanProxy.say()方法时,如果我们设置了MethodInterceptor的话,执行的逻辑实际是:

    var10000.intercept(this, CGLIB$say$0$Method, CGLIB$emptyArgs, CGLIB$say$0$Proxy);
    

    所以,也就是在这里,调用了intercept方法!

    再多说一句,这里的MethodInterceptor对象就是我们用enhancer的setCallback方法传进来的,即如下代码:

    MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
    myMethodInterceptor.bind(object);
    enhancer.setCallback(myMethodInterceptor);
    

好了,找到intercept方法的调用后,我们就可以开始理解他的四个参数了,为了便于阅读,我把intercept的声明也列出来:

// 声明
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {...}

// 调用 (在say()方法的上下文中)
var10000.intercept(this, CGLIB$say$0$Method, CGLIB$emptyArgs, CGLIB$say$0$Proxy);

// 相关的变量(在CGLIB$STATICHOOK1()中初始化)
CGLIB$emptyArgs = new Object[0];

CGLIB$say$0$Method = ReflectUtils.findMethods(new String[]{"say", "()V"}, (var1 = Class.forName("Human")).getDeclaredMethods())[0];

CGLIB$say$0$Proxy = MethodProxy.create(var1, var0, "()V", "say", "CGLIB$say$0");

这么一看,intercept方法的四个参数的含义也就比较清楚了:

  • Object o:代理对象本身

    通过调用时传入this很容易看出。

  • Method method: 被代理对象的方法

    通过CGLIB$say 0 0 0Method的初始化过程我们可以知道,他实际就指向了被代理类(Human)中对应的方法(say())。也就是说,你在代理对象上调用了方法fun,当进入到intercept函数时,method参数就是指向了被代理对象的fun方法。

  • Object[] objects:函数调用的参数

    通过CGLIB$emptyArgs我们也可以很容易的猜出这个参数实际就是函数调用时要传递的参数列表。在本例中,由于say()不需要任何参数,所以传个空参即可。

  • MethodProxy methodProxy:方法的代理

    这个是四个参数中最难理解的一个,下面对这个参数展开说明。

理解MethodProxy(重点!)

同样,我们还是从源码的角度进行分析,源码中创建MethodProxy的代码为:

Class var0 = Class.forName("Human$$EnhancerByCGLIB$$1a29a813");
var1 = Class.forName("Human");
CGLIB$say$0$Proxy = MethodProxy.create(var1, var0, "()V", "say", "CGLIB$say$0");

这里,var1就是我们的被代理对象,而var0则是代理对象本身,然后通过MethodProxy.create()方法创建一个MethodProxy,MethodProxy类的源码为:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package net.sf.cglib.proxy;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import net.sf.cglib.core.AbstractClassGenerator;
import net.sf.cglib.core.CodeGenerationException;
import net.sf.cglib.core.GeneratorStrategy;
import net.sf.cglib.core.NamingPolicy;
import net.sf.cglib.core.Signature;
import net.sf.cglib.reflect.FastClass;
import net.sf.cglib.reflect.FastClass.Generator;

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

    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 void init() {
        if (this.fastClassInfo == null) {
            synchronized(this.initLock) {
                if (this.fastClassInfo == null) {
                    MethodProxy.CreateInfo ci = this.createInfo;
                    MethodProxy.FastClassInfo fci = new MethodProxy.FastClassInfo();
                    fci.f1 = helper(ci, ci.c1);
                    fci.f2 = helper(ci, ci.c2);
                    fci.i1 = fci.f1.getIndex(this.sig1);
                    fci.i2 = fci.f2.getIndex(this.sig2);
                    this.fastClassInfo = fci;
                    this.createInfo = null;
                }
            }
        }

    }

    private static FastClass helper(MethodProxy.CreateInfo ci, Class type) {
        Generator g = new Generator();
        g.setType(type);
        g.setClassLoader(ci.c2.getClassLoader());
        g.setNamingPolicy(ci.namingPolicy);
        g.setStrategy(ci.strategy);
        g.setAttemptLoad(ci.attemptLoad);
        return g.create();
    }

	// 为了便于观看,省略部分非关键代码

    public static MethodProxy find(Class type, Signature sig) {
        try {
            Method m = type.getDeclaredMethod("CGLIB$findMethodProxy", MethodInterceptorGenerator.FIND_PROXY_TYPES);
            return (MethodProxy)m.invoke((Object)null, sig);
        } catch (NoSuchMethodException var3) {
            throw new IllegalArgumentException("Class " + type + " does not use a MethodInterceptor");
        } catch (IllegalAccessException var4) {
            throw new CodeGenerationException(var4);
        } catch (InvocationTargetException var5) {
            throw new CodeGenerationException(var5);
        }
    }

    public Object invoke(Object obj, Object[] args) throws Throwable {
        try {
            this.init();
            MethodProxy.FastClassInfo fci = this.fastClassInfo;
            return fci.f1.invoke(fci.i1, obj, args);
        } catch (InvocationTargetException var4) {
            throw var4.getTargetException();
        } catch (IllegalArgumentException var5) {
            if (this.fastClassInfo.i1 < 0) {
                throw new IllegalArgumentException("Protected method: " + this.sig1);
            } else {
                throw var5;
            }
        }
    }

    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 CreateInfo {
        // 省略
    }

    private static class FastClassInfo {
		// 省略
    }
}

友情提示:看下面的文字时,一定要特别关注是代理类,还是被代理类!

代码不算长,有一定编程经验的人应该都能看个大概(建议不要在这里看,最好能用IDE看,会方便很多),下面说一下看完代码后应得到的一些信息:

  • 代码里实际维护了两个东西,一个指向被代理的对象,一个指向代理对象。且和被代理对象相关的变量以1结尾,而与代理对象相关的变量则以2结尾

    这个通过create方法的源码即可知道。我们在调用create方法时,第一个参数var1,对应create方法中的形参class1,而var1指向的是被代理类,所以在MethodProxy类中,xxx1就是和被代理类有关;类似的,xxx2就是和代理类相关。这是最关键的一点!

  • 函数调用过程中涉及到了FastClass

    MethodProxy的两个重要方法就是invoke和invokeSuper,我们可以看到在他们的源码中,其实最后都是通过FastClass来完成了函数的调用。FastClass的原理就属于另外一个内容了,就不放在这篇文章里讲了。你现在只需要知道FastClass可以加速调用过程即可

我们在使用过程中,最主要的还是调用MethodProxy的invoke或者invokeSuper方法。所以我们再对这两个方法挖的深入一点:

public Object invoke(Object obj, Object[] args) throws Throwable {
     try {
         this.init();
         MethodProxy.FastClassInfo fci = this.fastClassInfo;
         return fci.f1.invoke(fci.i1, obj, args);
     } catch (InvocationTargetException var4) {
         throw var4.getTargetException();
     } catch (IllegalArgumentException var5) {
         if (this.fastClassInfo.i1 < 0) {
             throw new IllegalArgumentException("Protected method: " + this.sig1);
         } else {
             throw var5;
         }
     }
 }

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

从代码中可以看出,invoke和invokeSuper最主要的区别在于他们是用不同的FastClass完成了最终的调用,即:

// inivoke
return fci.f1.invoke(fci.i1, obj, args);
// invokeSuper
return fci.f2.invoke(fci.i2, obj, args);

回想我们前面说的,1就是和被代理类相关,2就是和代理类相关。所以,套用到这里,我们就可以对这两个语句进行解释:

  • invoke中的fci.f1.invoke(fci.i1, obj, args);表示用对象obj以参数args调用被代理类中函数描述为desc的name1方法

    desc和name1是什么呢?再回过头看看MethodPeoxy的create()函数:

    // 声明
    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;
    }
    // 调用
    CGLIB$say$0$Proxy = MethodProxy.create(var1, var0, "()V", "say", "CGLIB$say$0");
    

    可以发现,desc描述了这个函数的参数列表以及返回值,由于我们这里的say方法没有返回值且不接收参数,所以是()V。而name1则是函数在被代理类中的名字。在本文的例子中就是say。

  • invokeSuper中的fci.f2.invoke(fci.i2, obj, args);表示用对象obj以参数args调用代理类中函数描述为desc的name2方法

    和上面一样的分析方式,可以知道这里调用的是代理类的CGLIB$say$0方法,那这个方法是什么呢?回到源码中一看,发现这个方法是:

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

    这个方法很简单,只有一行,直接super.say();相当于直接调用了父类(也就是被代理类)的say方法。

好了,分析到这,我们对MethodProxy应该有个比较清晰的认识了,总结起来就是(一定要理解!):

  • MethodProxy本质上是对一个方法的代理。通过create函数,我们可以创建一个MethodProxy,并且指定被代理类(class1),代理类(class2),被代理方法的描述(desc),被代理方法在被代理类中的名字(name1),被代理方法在代理类中的名字(name2)

    这里对name2的解释可能不是非常准确。对于一个被代理类中的方法(如say()),代理类其实会为他生成两个方法(say()和CGLIB$say$0()):

    final void CGLIB$say$0() {
        super.say();
    }
    
    public final void say() {
        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$say$0$Method, CGLIB$emptyArgs, CGLIB$say$0$Proxy);
        } else {
            super.say();
        }
    }
    

    其中,和被代理类中的方法完全重名的方法是用来给外部调用的,这个方法实际重写了被代理类(也就是父类)的方法。逻辑就是要把函数调用转发到intercept函数中,从而实现对被代理对象的代理!还有一个函数带有CGLIB前缀和标号(CGLIB$say$0()),这个函数逻辑简单,就是直接调用被代理类的函数。我们这里说的name2就是指带有CGLIB前缀和标号的这个函数!

  • invoke(Object obj, Object[] args)函数的语义是用对象obj以参数args调用被代理类中函数描述为desc的name1方法

  • invokeSuper(Object obj, Object[] args)函数的语义是用对象obj以参数args调用代理类中函数描述为desc的name2方法

编写intercept逻辑

经过以上分析,我们已经对intercept的四个参数有了清晰的认识了。那最终的目的当然还是应用啦。所以最后我们来说一下如何编写intercept方法的逻辑。intercept方法的逻辑模板就是:

@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    // 1. pre-function logic
    
    // 2. invoke actual function

	// 3. post-function logic
	
	// return 
    return ret;
}

简单来说就是:

  1. 执行函数前的代理逻辑
  2. 调用被代理类的方法,执行函数
  3. 执行函数后的代理逻辑
  4. 将返回值返回

这里,第1,3步根据业务逻辑来写就行,第4步返回也没什么好说的。最关键的在于我们怎么调用被代理类的方法呢(即第2步)?

那这里就要用到我们前面对intercept参数的理解啦。我们可以用method来完成,也可以用methodProxy来完成。下面依次讲解全部可行的方法:

  1. 通过method.invoke(target, objects)

    这里target是我们维护的被代理对象,所以这句话相当于是说在target上用objects作为参数调用函数method,根据我们前面的分析,这个method实际就是Human类中的say方法,所以可以。

  2. 通过methodProxy.invoke(target, objects)

    根据前面的分析,这里的语义为“用target以objects为参数调用被代理类上的函数”,实际就是用target去调用了say,所以没问题。

  3. 通过methodProxy.invokeSuper(o, objects)

    同样的道理,根据前面的分析,这句话相当于是用o调用了CGLIB$say$0方法,也没问题。

虽然三种方式都可以,但还是推荐使用第2,3种方式,因为他们使用了FastClass,可以提升效率。第2种方式要求你在MyMethodInterceptor中维护一个被代理对象的实例target,而第3种方式则没有这个要求。


以上是三种可行的方式,还有三种不可行的方式也需要注意:

  1. method.invoke(o, objects)

    死循环。这句话相当于用o调用say方法,o中的say方法会一直调用intercept方法,intercept方法又调用say方法,从而导致死循环,stack overflow!

  2. methodProxy.invokeSuper(target, objects);

    运行报错!target是被代理类,也就是父类,这句话的语义相当于你要用target去调用CGLIB$say$0方法,而这个方法是在子类中才有的!所以会报错!

  3. methodProxy.invoke(o, objects);

    死循环。这句话引起死循环的原因和1非常类似。相当于你要在代理类上调用被代理类的say方法,所以最终会分配到intercept,那死循环就出现了!say—>intercept—>say---->intercept,最后stack overflow!

好了,到此为止,我们已经分析了所有可能的调用方式以及他们的正确性。实际上,这些方式完全不用背,只要你理解了本文所分析的内容,看到某一种调用方式,你就能分析出他的语义,从而推断出他的正确性。

最后,再放一下完整的测试代码,以下代码可以用于测试所有6种方式。

import net.sf.cglib.core.DebuggingClassWriter;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;


class Human {
    public void say() {
        System.out.println("I am super man~~~");
    }
}

class ProxyWithCglib {
    public static Object newProxy(Object object) {
        Enhancer enhancer = new Enhancer();

        enhancer.setSuperclass(object.getClass());

        MyMethodInterceptor myMethodInterceptor = new MyMethodInterceptor();
        myMethodInterceptor.bind(object);
        enhancer.setCallback(myMethodInterceptor);

        return enhancer.create();
    }
}

class MyMethodInterceptor implements MethodInterceptor {

    private Object target;

    public void bind(Object o) {
        this.target = o;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("pre-function logic.");

//        Object ret = method.invoke(target, objects); // 可以
//        Object ret = method.invoke(o, objects); // 死循环
//        Object ret = methodProxy.invokeSuper(o, objects); // 可以
        Object ret = methodProxy.invokeSuper(target, objects); // 运行时报错
//        Object ret = methodProxy.invoke(target, objects); // 可以
//        Object ret = methodProxy.invoke(o, objects); // 死循环

        System.out.println("post-function logic.");
        return ret;
    }
}

public class Test {
    public static void main(String[] args) {
        System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "./debug_info");
        Human humanProxy = (Human) ProxyWithCglib.newProxy(new Human());
        System.out.println(humanProxy.getClass());
        humanProxy.say();
    }
}

写在最后

总结一下,本文旨在通过源码层面的分析来更好的理解CGLIB动态代理中的关键方法intercept,回答了如何在intercept中完成对被代理方法的调用这一绝大多数博客都没有明确说明的问题。其中,对于MethodProxy的理解是本文的关键。最后,本文对所有可能的六种调用方式进行了分析,说明了他们的语义及正确性。希望本文能对你有所帮助。


最后的最后:源码分析类文章不太会写,欢迎批评指正!

 类似资料: