Spring之动态代理源码

前言

Spring之AOP的最后,我们提到了两种动态代理的方式:JDK动态代理以及cglib动态代理;JDK方式可以代理接口或者实现了接口的实现类,cglib的被代理类不能是接口/抽象类。本文基于JDK1.8

CGLIB

之前是在项目里引入spring全家桶来看源码的,所以一开始看Enhancer是在org.springframework下还以为是Spring自己重写的一套。通过jar包方式查看,发现很多相关的类都不能看到源码,就去github上拉了springframework;在spring-core这个模块下的spring-core.gradle有这么一段:

task cglibRepackJar(type: ShadowJar) {
	baseName = 'spring-cglib-repack'
	version = cglibVersion
	configurations = [project.configurations.cglib]
	relocate 'net.sf.cglib', 'org.springframework.cglib'
	relocate 'org.objectweb.asm', 'org.springframework.asm'
}

就是cglib用的还是net.sf.cglib,于是直接下了net.sf.cglib源码。附上地址:https://gitee.com/mirrors/cglib

源码

使用

我们先看一个例子,并想下打印出来是怎样的。

public class MyEnhancerTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyTest.class);
        enhancer.setCallback(new MyInterceptor());
        MyTest myTest = (MyTest) enhancer.create();
        myTest.print("hello world");
    }

    static class MyInterceptor implements MethodInterceptor {
        @Override
        public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
            System.out.println("前置操作");
            Object ret = proxy.invokeSuper(obj, args);
            System.out.println("后置操作");
            return ret;
        }
    }

    public void print(String input) {
        System.out.println(input + ":" + this.getClass().getName());
    }
}

现在我们看下打印的内容:

前置操作
hello world:MyEnhancerTest$$EnhancerByCGLIB$$49c4991a
后置操作

看打印的内容我们知道代理成功了,并且新的代理类名为MyEnhancerTest$$EnhancerByCGLIB$$49c4991a

输出代理类

在单元测试方法中加上

System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "target/cglib");

junit

就可以在target/cglib目录下输出cglib动态生成的类。
在这里插入图片描述
先了解下这3个类继承了什么父类,实现了什么接口。

public class MyEnhancerTest$$EnhancerByCGLIB$$49c4991a extends MyEnhancerTest implements Factory{}
public class MyEnhancerTest$$EnhancerByCGLIB$$49c4991a$$FastClassByCGLIB$$285c3a4a extends FastClass{}
public class MyEnhancerTest$$FastClassByCGLIB$$ef1ff622 extends FastClass{}

CGLIB是利用ASM框架来生成字节码文件的。ASM 是一个Java字节码操控框架。它能被用来动态生成类或者增强既有类的功能。ASM可以直接产生二进制class文件,也可以在类被加载入Java虚拟机之前动态改变类行为。

分析

使用中,我们看到打印出来的类名就是MyEnhancerTest$$EnhancerByCGLIB$$49c4991a,我们就从这个类开始进行分析吧。
类的内容比较多,我们按需粘贴代码进行分析,最好是在自己机器上执行一遍,看下类的全貌。

一进来首先是一个静态代码块CGLIB$STATICHOOK1()方法,CGLIB$STATICHOOK1()主要是初始化一些类属性。

static {
    CGLIB$STATICHOOK1();
}

static void CGLIB$STATICHOOK1() {
    CGLIB$THREAD_CALLBACKS = new ThreadLocal();
    CGLIB$emptyArgs = new Object[0];
    // 代理类
    Class var0 = Class.forName("MyEnhancerTest$$EnhancerByCGLIB$$49c4991a");
    // 父类,会根据方法而变动
    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$equals$2$Method = var10000[0];
    CGLIB$equals$2$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$2");
    CGLIB$toString$3$Method = var10000[1];
    CGLIB$toString$3$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$3");
    CGLIB$hashCode$4$Method = var10000[2];
    CGLIB$hashCode$4$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$4");
    CGLIB$clone$5$Method = var10000[3];
    CGLIB$clone$5$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$5");
    // 这里var1赋值成`MyEnhancerTest`
    var10000 = ReflectUtils.findMethods(new String[]{"print", "(Ljava/lang/String;)V", "test", "()V"}, (var1 = Class.forName("MyEnhancerTest")).getDeclaredMethods());
    CGLIB$print$0$Method = var10000[0];
    // 根据父类/子类,以及两个类里的方法名生成`MethodProxy`, `MethodProxy`很重要,我们的`MethodInterceptor::intercept`就传入了这个参数
    CGLIB$print$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)V", "print", "CGLIB$print$0");
    CGLIB$test$1$Method = var10000[1];
    CGLIB$test$1$Proxy = MethodProxy.create(var1, var0, "()V", "test", "CGLIB$test$1");
}

MethodProxy.create方法将父/子类,以及方法信息存到MethodProxy中,后续会用到。

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 CreateInfo(c1, c2);
    return proxy;
}
public CreateInfo(Class c1, Class c2) {
    this.c1 = c1;
    this.c2 = c2;
    AbstractClassGenerator fromEnhancer = AbstractClassGenerator.getCurrent();
    if (fromEnhancer != null) {
        namingPolicy = fromEnhancer.getNamingPolicy();
        strategy = fromEnhancer.getStrategy();
        attemptLoad = fromEnhancer.getAttemptLoad();
    }
}

当我们调用print方法时,执行的是这么一段代码。

public final void print(String var1) {
    // 我们之前在`Enhancer`里设置的`callback`, 这里就是`MyEnhancerTest.MyInterceptor`
    MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
    if (var10000 == null) {
        CGLIB$BIND_CALLBACKS(this);
        var10000 = this.CGLIB$CALLBACK_0;
    }

    if (var10000 != null) {
        // 看到这里执行里`MyInterceptor`的`intercept`方法, 传入了当前对象,`MyEnhancerTest.print`的方法反射对象, 入参,以及`CGLIB$print$0$Proxy`(`CGLIB$print$0$Proxy`上文提过,是一个`MethodProxy`里面存储了父/子类以及`print`
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值