红茶的个人站点

  • 首页
  • 专栏
  • 开发工具
  • 其它
  • 隐私政策
Awalon
Talk is cheap,show me the code.
  1. 首页
  2. 专栏
  3. Spring Boot 学习笔记
  4. 正文

Spring 源码学习 7:动态代理

2025年6月24日 11点热度 0人点赞 0条评论

实现

动态代理有两种实现方式:

  • JDK

  • CGLIB

JDK

要代理的目标类:

interface DoSomething {
    void doSomething();
}
​
@Slf4j
static class Target implements DoSomething {
​
    @Override
    public void doSomething() {
        log.info("doSomething...");
    }
}

通过 JDK 创建动态代理:

Target target = new Target();
DoSomething proxyInstance = (DoSomething)Proxy.newProxyInstance(
    this.getClass().getClassLoader(),
    new Class[]{DoSomething.class},
    new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            log.info("before invoke");
            Object result = method.invoke(target, args);
            log.info("after invoke");
            return result;
        }
    });
proxyInstance.doSomething();

JDK 动态代理是基于接口的,也就是代理对象和目标对象具有相同的接口(这里是DoSomething),这也体现在newProxyInstance方法接收的表示接口实现的Class类型的数组(new Class[]{DoSomething.class})。

CGLIB

要代理的目标类:

@Slf4j
static class Target{
    public void doSomething(){
        log.info("doSomething");
    }
}

使用 CGLIB 实现动态代理:

Target target = new Target();
Target proxyInstance = (Target)Enhancer.create(Target.class, new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        log.info("before...");
        Object result = method.invoke(target, args);
        log.info("after...");
        return result;
    }
});
proxyInstance.doSomething();

与 JDK 不同,CGLIB 通过继承目标类的方式实现代理,因此存在一些限制,比如目标类不能是final(无法被继承),代理的方法不能是final(无法被重写)。

除了使用方法的反射调用method.invoke(target, args),CGLIB 还支持通过其它方式调用被代理对象的方法:

log.info("before...");
Object result = proxy.invoke(target, args);
log.info("after...");

还可以:

log.info("before...");
Object result = proxy.invokeSuper(obj, args);
log.info("after...");

需要注意的是,最后一种方式没有指定被代理的对象target,而是使用了代理对象obj,显然是 CGLIB 自己临时创建一个被代理对象再执行。

原理

JDK

先用代理模式编写一个对目标类型实现代理的代码。目标类型:

interface DoSomething{
    void doSomething();
}
static class Target implements DoSomething{
​
    @Override
    public void doSomething() {
        System.out.println("Target doSomething");
    }
}

代理类型:

static class $Proxy0 implements DoSomething{
    private DoSomething target;
    public $Proxy0(DoSomething target) {
        this.target = target;
    }
​
    @Override
    public void doSomething() {
        System.out.println("before...");
        target.doSomething();
        System.out.println("after...");
    }
}

测试用例:

Target target = new Target();
$Proxy0 proxy = new $Proxy0(target);
proxy.doSomething();

这样做不够灵活,增强被代理对象的代码固定在代理类型的doSomething方法中,更为灵活的方式是由客户端代码通过匿名函数的方式指定具体实现。

定义一个作为匿名函数的接口:

@FunctionalInterface
interface InvokeHandler{
    void invoke();
}

修改代理类,不再直接持有被代理对象,直接使用匿名函数:

static class $Proxy0 implements DoSomething{
    private InvokeHandler invokeHandler;
    public $Proxy0(InvokeHandler invokeHandler) {
        this.invokeHandler = invokeHandler;
    }

    @Override
    public void doSomething() {
        invokeHandler.invoke();
    }
}

测试用例:

Target target = new Target();
$Proxy0 proxy = new $Proxy0(()->{
    System.out.println("before...");
    target.doSomething();
    System.out.println("after...");
});
proxy.doSomething();

现在代理类型对被代理方法的执行逻辑由客户端代码定义,更为灵活。

如果被代理类型中包含多个方法:

interface DoSomething {
    void doSomething();

    void doSomethingElse();
}

上面的实现就会遇到一些问题,这里因为代理类型中对任何方法调用都是通过匿名函数:

static class $Proxy0 implements DoSomething {
    private InvokeHandler invokeHandler;

    public $Proxy0(InvokeHandler invokeHandler) {
        this.invokeHandler = invokeHandler;
    }

    @Override
    public void doSomething() {
        invokeHandler.invoke();
    }

    @Override
    public void doSomethingElse() {
        invokeHandler.invoke();
    }
}

因此,如果测试用例不修改,就会出现问题:

Target target = new Target();
$Proxy0 proxy = new $Proxy0(() -> {
    System.out.println("before...");
    target.doSomething();
    System.out.println("after...");
});
proxy.doSomething();
proxy.doSomethingElse();

这里显然匿名函数中不能写死target.doSomething,而是需要知道代理对象实际执行的是哪个方法,再调用对应的被代理方法。

修改匿名函数接口,接收一个 Method 类型参数,这样客户端代码就知道要调用哪个原始方法了:

@FunctionalInterface
interface InvokeHandler {
    void invoke(Method method, Object[] args) throws Throwable;
}

代理类型中调用匿名函数时传入对应的方法对象:

@Override
public void doSomething() {
    try {
        invokeHandler.invoke(DoSomething.class.getMethod("doSomething"), null);
    } catch (RuntimeException | Error e) {
        throw e;
    } catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
}

测试用例:

Target target = new Target();
$Proxy0 proxy = new $Proxy0((method, args) -> {
    System.out.println("before...");
    method.invoke(target, args);
    System.out.println("after...");
});
proxy.doSomething();
proxy.doSomethingElse();

要代理的方法有参数和返回值:

interface DoSomething {
    void doSomething();

    void doSomethingElse();

    int plus(int a, int b);
}

需要修改匿名函数返回Object:

@FunctionalInterface
interface InvokeHandler {
    Object invoke(Method method, Object[] args) throws Throwable;
}

在代理类型中使用参数和返回值:

@Override
public int plus(int a, int b) {
    int result;
    try {
        Object[] args = {a, b};
        result = (int)invokeHandler.invoke(DoSomething.class.getMethod("plus", int.class, int.class), args);
    } catch (RuntimeException | Error e) {
        throw e;
    } catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
    return result;
}

现在自定义的InvokeHandler接口已经与 JDK 的InvocationHandler很接近了,只不过后者还暴露了一个代理对象:

@FunctionalInterface
interface InvokeHandler {
    Object invoke(Object proxy, Method method, Object[] args) throws Throwable;
}

使用 Arthas 工具查看 JDK 生成的动态代理的字节码:

final class $Proxy8
    extends Proxy
    implements JdkProxyTests.DoSomething {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;

    public $Proxy8(InvocationHandler invocationHandler) {
        super(invocationHandler);
    }
    static {
        ClassLoader classLoader = $Proxy8.class.getClassLoader();
        try {
            m0 = Class.forName("java.lang.Object", false, classLoader).getMethod("hashCode", new Class[0]);
            m1 = Class.forName("java.lang.Object", false, classLoader).getMethod("equals", Class.forName("java.lang.Object", false, classLoader));
            m2 = Class.forName("java.lang.Object", false, classLoader).getMethod("toString", new Class[0]);
            m3 = Class.forName("cn.icexmoon.proxy.JdkProxyTests$DoSomething", false, classLoader).getMethod("doSomething", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }
    // ...

    public final void doSomething() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}

可以看到其实现与我们的实现非常类似。

ASM

当然,JDK 是通过程序的方式创建代理类型的字节码,更准确的说,是使用 ASM 动态生成字节码。

为了方便使用 ASM,需要先将内部类形式的代理类转换为单独的类:

class $Proxy0 extends Proxy implements HowToWorkTests6.DoSomething {

    public $Proxy0(InvocationHandler invokeHandler) {
        super(invokeHandler);
    }

    @Override
    public void doSomething() {
        try {
            h.invoke(this, HowToWorkTests6.DoSomething.class.getMethod("doSomething"), null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void doSomethingElse() {
        try {
            h.invoke(this, HowToWorkTests6.DoSomething.class.getMethod("doSomethingElse"), null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public int plus(int a, int b) {
        int result;
        try {
            Object[] args = {a, b};
            result = (int) h.invoke(this, HowToWorkTests6.DoSomething.class.getMethod("plus", int.class, int.class), args);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
        return result;
    }
}

安装 Idea 插件 ASM Bytecode Outline Rebooted。

在需要查看 ASM 代码的类文件(这里是代理类$Proxy0.java)中右键选择Show Bytecode Outline:

image-20250624103044047

打开的窗口分为三个标签页:

image-20250624103231793

Bytecode是这个类被编译后的字节码,ASMified 是用 ASM 框架生成这个类的字节码的代码。

将ASMified中的代码拷贝并创建一个新的类:

image-20250624103540287

利用这个 Dump 类生成代理类的字节码:

// 生成代理类的字节码
byte[] dump = $Proxy0Dump.dump();
// 将字节码写入文件
String filePath = "D:\\workspace\\learn-spring-source\\ch6\\proxy\\target\\$Proxy0.class";
FileOutputStream fileOutputStream = new FileOutputStream(filePath);
fileOutputStream.write(dump);
fileOutputStream.close();

可以在目标位置看到生成的字节码文件:

image-20250624111345113

内容也与我们编写的完全一致。

实际上并不一定要生成字节码文件,可以直接通过类加载器从内存中加载类的字节码:

byte[] dump = $Proxy0Dump.dump();
// 创建类加载器
final String CLASS_NAME = "cn.icexmoon.proxy.$Proxy0";
ClassLoader classLoader = new ClassLoader() {
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        if (name.equals(CLASS_NAME)) {
            return super.defineClass(CLASS_NAME, dump, 0, dump.length);
        }
        return super.findClass(name);
    }
};
// 利用反射创建代理对象
Class<?> cls = classLoader.loadClass(CLASS_NAME);

之后就是利用反射创建代理对象了:

Constructor<?> constructor = cls.getConstructor(InvocationHandler.class);
HowToWorkTests6.DoSomething proxyInstance = (HowToWorkTests6.DoSomething)constructor.newInstance(new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Target target = new Target();
        System.out.println("before invoke");
        Object result = method.invoke(target, args);
        System.out.println("after invoke");
        return result;
    }
});
proxyInstance.doSomething();
proxyInstance.doSomethingElse();
int plus = proxyInstance.plus(1, 2);
System.out.println(plus);

通过这个过程,我们可以体会 JDK 是怎么利用 ASM 框架动态创建代理类的字节码,然后通过类加载器加载这些字节码,并最终创建代理对象。

CGLIB

类似的,可以仿照 JDK 实现代理的方式编写一个代理类,模拟 CGLIB 的实现方式:

public class $Proxy0 extends Target {
    private final MethodInterceptor methodInterceptor;
    private static final Method METHOD_DO_SOMETHING;
    private static final Method METHOD_DO_SOMETHING_ELSE;
    private static final Method METHOD_PLUS;

    static {
        try {
            METHOD_DO_SOMETHING = Target.class.getDeclaredMethod("doSomething");
            METHOD_DO_SOMETHING_ELSE = Target.class.getDeclaredMethod("doSomethingElse");
            METHOD_PLUS = Target.class.getDeclaredMethod("plus", int.class, int.class);
        } catch (NoSuchMethodException e) {
            throw new NoSuchMethodError(e.getMessage());
        }
    }


    public $Proxy0(MethodInterceptor methodInterceptor) {
        this.methodInterceptor = methodInterceptor;
    }

    @Override
    public void doSomething() {
        try {
            Object[] args = new Object[0];
            methodInterceptor.intercept(this, METHOD_DO_SOMETHING, args, null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public void doSomethingElse() {
        try {
            Object[] args = new Object[0];
            methodInterceptor.intercept(this, METHOD_DO_SOMETHING_ELSE, args, null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    @Override
    public int plus(int a, int b) {
        try {
            Object[] args = {a, b};
            return (int) methodInterceptor.intercept(this, METHOD_PLUS, args, null);
        } catch (RuntimeException | Error e) {
            throw e;
        } catch (Throwable e) {
            throw new UndeclaredThrowableException(e);
        }
    }
}

与 JDK 代理不同的是,CGLIB 采用继承而非实现接口的方式实现代理。此外,CGLIB 代理使用的匿名函数接口类型是MethodInterceptor,位于org.springframework.cglib包下。

Spring 内置了 CGLIB 库,即org.springframework.cglib。

测试用例:

Target target = new Target();
Target proxyInstance = (Target)new $Proxy0(new MethodInterceptor() {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before...");
        Object result = method.invoke(target, args);
        System.out.println("after...");
        return result;
    }
});
proxyInstance.doSomething();
proxyInstance.doSomethingElse();
int plus = proxyInstance.plus(1, 2);
System.out.println("plus : " + plus);

MethodProxy

与InvocationHandler接口不同的是,MethodInterceptor接口除了要提供代理对象、方法对象、参数列表。还需要提供一个MethodProxy对象。客户端代码可以利用这个对象实现非反射的原始方法调用。

下面继续用模拟的方式看这个对象是怎么工作的。

首先,需要在代理类型中添加一些与被代理方法对应的原始调用:

// 利用 super 构建方法的原始调用
public void doSomethingSuper(){
    super.doSomething();
}

public void doSomethingElseSuper(){
    super.doSomethingElse();
}

public int plusSuper(int a, int b){
    return super.plus(a, b);
}

因为代理类型与原始类型是继承关系,所以很容易通过super实现这一点。

声明并初始化代理方法对应的MethodProxy对象:

// 声明 MethodProxy
private static MethodProxy METHOD_DO_SOMETHING_PROXY;
private static MethodProxy METHOD_DO_SOMETHING_ELSE_PROXY;
private static MethodProxy METHOD_PLUS_PROXY;
// 初始化
static {
    METHOD_DO_SOMETHING_PROXY = MethodProxy.create(Target.class, $Proxy1.class, "()V", "doSomething","doSomethingSuper");
    METHOD_DO_SOMETHING_ELSE_PROXY = MethodProxy.create(Target.class, $Proxy1.class, "()V", "doSomethingElse", "doSomethingElseSuper");
    METHOD_PLUS_PROXY = MethodProxy.create(Target.class, $Proxy1.class, "(II)I", "plus", "plusSuper");
}

MethodProxy.create的接收的参数依次为:

  • 代理类型

  • 原始类型

  • 字节码形式的方法签名

  • 被代理的方法名

  • 通过 super 调用实现的原始调用方法名

这其中()V这种是比较少见的字节码形式的方法签名,()V表示参数列表为空,返回值类型为Void。相应的,(II)I表示参数列表为(int, int),返回值类型为int。其实可以借助之前使用的插件查看原始类型的字节码来确认字节码中的方法签名:

image-20250624152158564

现在只需要在methodInterceptor调用时传入 MethodProxy 即可:

@Override
public void doSomething() {
    try {
        Object[] args = new Object[0];
        methodInterceptor.intercept(this, METHOD_DO_SOMETHING, args, METHOD_DO_SOMETHING_PROXY);
    } catch (RuntimeException | Error e) {
        throw e;
    } catch (Throwable e) {
        throw new UndeclaredThrowableException(e);
    }
}

在客户端代码中,可以利用MethodProxy实现非反射调用原始方法:

Target proxyInstance = new $Proxy1(new MethodInterceptor() {
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before...");
        Object result = methodProxy.invoke(target, args);
        System.out.println("after...");
        return result;
    }
});

还可以:

Object result = methodProxy.invokeSuper(proxy, args);

当然,后者是通过代理对象调用,所以实际使用的是新的原始类型实例。

实现

下面通过模拟的方式展示 CGLIB 怎么通过MethodProxy实现非反射方式的原始方法调用。

首先自定义一个MethodProxy类:

@Data
public class MethodProxy {
    @Data
    static class FastClassInfo {
        private FastClass fastClass;
        private int index;
    }

    private FastClassInfo proxyFastClassInfo;
    private FastClassInfo originalFastClassInfo;

    public static MethodProxy create(Class OriginalClass, Class ProxyClass, String desc, String originalMethodName, String superMethodName) {
        // 设置原始类型的 fastClassInfo
        MethodProxy newProxyMethod = new MethodProxy();
        FastClassInfo originalFastClassInfo = new FastClassInfo();
        TargetFastClass targetFastClass = new TargetFastClass();
        originalFastClassInfo.setFastClass(targetFastClass);
        int index = targetFastClass.getIndex(new Signature(originalMethodName, desc));
        originalFastClassInfo.setIndex(index);
        newProxyMethod.setOriginalFastClassInfo(originalFastClassInfo);
        // 设置代理类型的 fastClassInfo
        FastClassInfo proxyFastClassInfo = new FastClassInfo();
        ProxyFastClass proxyFastClass = new ProxyFastClass();
        proxyFastClassInfo.setFastClass(proxyFastClass);
        index = proxyFastClass.getIndex(new Signature(superMethodName, desc));
        proxyFastClassInfo.setIndex(index);
        newProxyMethod.setProxyFastClassInfo(proxyFastClassInfo);
        return newProxyMethod;
    }

    public Object invoke(Object originalObj, Object[] args) throws Throwable {
        return originalFastClassInfo.getFastClass().invoke(originalFastClassInfo.getIndex(), originalObj, args);
    }

    public Object invokeSuper(Object proxyObj, Object[] args) throws Throwable {
        return proxyFastClassInfo.getFastClass().invoke(proxyFastClassInfo.getIndex(), proxyObj, args);
    }
}

这个类中分别保存了代理类和原始类的FastClassInfo对象,其中包含一个用于调用原始方法的FastClass对象以及表示当前方法在FastClass对象中的索引。

分别对原始类和代理类实现FastClass类,这里只展示原始类的实现,代理类的请查看源码。

public class TargetFastClass extends FastClass {
    public TargetFastClass() {
        super(Target.class);
    }

    private static final MethodDefinition METHOD_DO_SOMETHING_DEFINITION;
    private static final MethodDefinition METHOD_DO_SOMETHING_ELSE_DEFINITION;
    private static final MethodDefinition METHOD_PLUS_DEFINITION;
    private static final MethodDefinition[] methodDefinitions;

    static {
        METHOD_DO_SOMETHING_DEFINITION = new MethodDefinition(0, "doSomething", new Class[0], new Signature("doSomething", "()V"));
        METHOD_DO_SOMETHING_ELSE_DEFINITION = new MethodDefinition(1, "doSomethingElse", new Class[0], new Signature("doSomethingElse", "()V"));
        METHOD_PLUS_DEFINITION = new MethodDefinition(2, "plus", new Class[]{int.class, int.class}, new Signature("plus", "(II)I"));
        methodDefinitions = new MethodDefinition[]{METHOD_DO_SOMETHING_DEFINITION, METHOD_DO_SOMETHING_ELSE_DEFINITION, METHOD_PLUS_DEFINITION};
    }


    @Override
    public int getIndex(String name, Class[] parameterTypes) {
        for (MethodDefinition methodDefinition : methodDefinitions) {
            if (methodDefinition.equals(name, parameterTypes)) {
                return methodDefinition.getIndex();
            }
        }
        return -1;
    }

    @Override
    public int getIndex(Class[] parameterTypes) {
        for (MethodDefinition methodDefinition : methodDefinitions) {
            if (methodDefinition.equals(parameterTypes)) {
                return methodDefinition.getIndex();
            }
        }
        return -1;
    }

    @Override
    public Object invoke(int index, Object obj, Object[] args) throws InvocationTargetException {
        Object result = null;
        if (METHOD_DO_SOMETHING_DEFINITION.getIndex() == index) {
            ((Target) obj).doSomething();
        } else if (METHOD_DO_SOMETHING_ELSE_DEFINITION.getIndex() == index) {
            ((Target) obj).doSomethingElse();
        } else if (METHOD_PLUS_DEFINITION.getIndex() == index) {
            result = ((Target) obj).plus((int) args[0], (int) args[1]);
        } else {
            throw new NoSuchMethodError("index is " + index);
        }
        return result;
    }

    @Override
    public Object newInstance(int index, Object[] args) throws InvocationTargetException {
        // 示例中只有一个构造器
        return new Target();
    }

    @Override
    public int getIndex(Signature sig) {
        for (MethodDefinition methodDefinition : methodDefinitions) {
            if (methodDefinition.equals(sig)) {
                return methodDefinition.getIndex();
            }
        }
        return -1;
    }

    @Override
    public int getMaxIndex() {
        return methodDefinitions.length - 1;
    }
}

因为 MethodProxy 创建时就保存了方法索引,所以在调用时直接在 FastClass 中按照索引直接调用对应的方法,而非是反射调用。

为了方便比较方法定义,这里自定义了一个MethodDefinition 以保存方法信息:

public class MethodDefinition {
    @Getter
    private int index;
    private String name;
    private Class[] parameterTypes;
    private Signature signature;

    public MethodDefinition(int index, String name, Class[] parameterTypes, Signature signature) {
        this.index = index;
        this.name = name;
        this.parameterTypes = parameterTypes;
        this.signature = signature;
    }

    public boolean equals(String name, Class[] parameterTypes) {
        if (!(name.equals(this.name) && parameterTypes.length == this.parameterTypes.length)) {
            return false;
        }
        for (int i = 0; i < parameterTypes.length; i++) {
            if (!parameterTypes[i].equals(this.parameterTypes[i])) {
                return false;
            }
        }
        return true;
    }

    public boolean equals(Class[] parameterTypes) {
        if (parameterTypes.length != this.parameterTypes.length) {
            return false;
        }
        for (int i = 0; i < parameterTypes.length; i++) {
            if (!parameterTypes[i].equals(this.parameterTypes[i])) {
                return false;
            }
        }
        return true;
    }

    public boolean equals(Signature signature) {
        if (this.signature.equals(signature)){
            return true;
        }
        return false;
    }
}

为了使用自定义的MethodProxy,还需要自定义一个MethodInterceptor:

public interface MethodInterceptor {
    Object intercept(Object obj, java.lang.reflect.Method method, Object[] args,
                     MethodProxy proxy) throws Throwable;
}

测试用例与之前没有什么不同,只不过使用MethodInterceptor改为自定义类型:

Target target = new Target();
Target proxyInstance = new $Proxy2(new MethodInterceptor() {
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println("before...");
        Object result = methodProxy.invoke(target, args);
        System.out.println("after...");
        return result;
    }
});
proxyInstance.doSomething();
proxyInstance.doSomethingElse();
int plus = proxyInstance.plus(1, 2);
System.out.println("plus : " + plus);

避免反射调用的好处是可以优化性能。

本文的完整示例代码可以从这里获取。

The End.

参考资料

  • java - ASM入门篇 - ksfzhaohui技术专栏 - SegmentFault 思否

  • 黑马程序员Spring视频教程,深度讲解spring5底层原理

本作品采用 知识共享署名 4.0 国际许可协议 进行许可
标签: cglib spring 代理
最后更新:2025年6月24日

魔芋红茶

加一点PHP,加一点Go,加一点Python......

点赞
< 上一篇

文章评论

razz evil exclaim smile redface biggrin eek confused idea lol mad twisted rolleyes wink cool arrow neutral cry mrgreen drooling persevering
取消回复

COPYRIGHT © 2021 icexmoon.cn. ALL RIGHTS RESERVED.
本网站由提供CDN加速/云存储服务

Theme Kratos Made By Seaton Jiang

宁ICP备2021001508号

宁公网安备64040202000141号