《知识点扫盲 · 动态代理》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗

🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数,欢迎多多交流。👍

文章目录

写在前面的话

之前几篇文章介绍 Spring 和 SpringMVC 源码中,经常看到反射机制和动态代理的源码,本篇展开介绍一下。

推荐文章 - 程序猿入职必会:
《程序猿入职必会(1) · 搭建拥有数据交互的 SpringBoot 》
《程序猿入职必会(2) · 搭建具备前端展示效果的 Vue》
《程序猿入职必会(3) · SpringBoot 各层功能完善 》
《程序猿入职必会(4) · Vue 完成 CURD 案例 》
《程序猿入职必会(5) · CURD 页面细节规范 》
《程序猿入职必会(6) · 返回结果统一封装》
《程序猿入职必会(7) · 前端请求工具封装》
《程序猿入职必会(8) · 整合 Knife4j 接口文档》
《程序猿入职必会(9) · 用代码生成器快速开发》
《程序猿入职必会(10) · 整合 Redis(基础篇)》
《程序猿入职必会(11) · 整合 Redis 实战运用》

推荐文章 - 学会 SpringMVC 系列
《学会 SpringMVC 系列 · 基础篇》
《学会 SpringMVC 系列 · 剖析篇(上)》
《学会 SpringMVC 系列 · 剖析入参处理》
《学会 SpringMVC 系列 · 剖析出参处理》
《学会 SpringMVC 系列 · 返回值处理器》
《学会 SpringMVC 系列 · 消息转换器 MessageConverters》
《学会 SpringMVC 系列 · 写入拦截器 ResponseBodyAdvice》
《学会 SpringMVC 系列 · 剖析初始化》
《学会 SpringMVC 系列 · 参数解析器 ArgumentResolvers》


动态代理

技术简介

动态代理在Java中有着广泛的应用,比如 Spring AOP、RPC 远程调用、Java 注解对象获取、日志、用户鉴权、全局性异常处理、性能监控,甚至事务处理等。

代理模式

当我们谈及 Java 的 动态代理 或者是 静态代理 ,我们都很容易谈及到设计模式中的代理模式。

代理模式给某一个对象提供一个代理,并由代理对象来控制对真实对象的访问,起到对代理对象已有功能的增强。

代理模式存在三个角色:

1、抽象主题角色(Subject)

抽象主题角色指的是 真实对象 与 代理对象 的公共的法所抽象出来的一个接口或者是抽象类。

在 Java 编程成,我们就可以认为是一个 接口 或者是 抽象类 。

2、真实主题角色(RealSubject)

真实主题角色指的是被代理对象。

3、代理主题角色(ProxySubject)

代理主题角色指的是代理对象。


两种代理

JDK 动态代理

适用场景:JDK 动态代理仅适用于接口代理。如果你的 Bean 实现了一个或多个接口,Spring 会使用 JDK 动态代理。

工作原理:JDK 动态代理通过反射机制创建一个实现了指定接口的代理类,并在调用方法时将调用转发到目标对象。
CGLIB 代理

适用场景:CGLIB 代理适用于没有实现接口的类,或者当你明确指定使用 CGLIB 代理时。CGLIB 通过生成目标类的子类来实现代理。

工作原理:CGLIB 通过字节码生成技术在运行时创建一个目标类的子类,并重写其方法,以实现代理功能。


JDK 动态代理

这里也来一个简单示例:

java 复制代码
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 定义一个接口
interface Hello {
    void sayHello(String name);
}

// 实现 InvocationHandler 接口
class HelloInvocationHandler implements InvocationHandler {
    private final Hello target;

    public HelloInvocationHandler(Hello target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = method.invoke(target, args); // 调用目标对象的方法
        System.out.println("After method: " + method.getName());
        return result;
    }
}

// 目标类实现 Hello 接口
class HelloImpl implements Hello {
    @Override
    public void sayHello(String name) {
        System.out.println("Hello, " + name);
    }
}

// 使用动态代理
public class ProxyExample {
    public static void main(String[] args) {
        Hello hello = new HelloImpl();
        Hello proxyInstance = (Hello) Proxy.newProxyInstance(
                hello.getClass().getClassLoader(),
                new Class<?>[]{Hello.class},
                new HelloInvocationHandler(hello)
        );

        proxyInstance.sayHello("World");
    }
}

上面的示例可以看出,核心就是 Proxy.newProxyInstance 方法,下面介绍一下。

【Proxy.newProxyInstance】

Proxy.newProxyInstance 是 Java 提供的一个静态方法,用于创建动态代理对象。这个方法属于java.lang.reflect.Proxy类,允许你在运行时创建一个实现了一组给定接口的新代理实例。这个代理实例可以在调用方法时,将调用转发到一个指定的处理器,通常是实现了 InvocationHandler 接口的对象。

java 复制代码
//参数1:ClassLoader loader:用于加载代理类的类加载器。通常可以使用目标类的类加载器。
//参数2:Class<?>[] interfaces:一个接口数组,代理对象将实现这些接口。
//参数3:InvocationHandlerh:一个实现了 InvocationHandler 接口的对象,负责处理代理实例上调用的方法。
//返回一个代理对象,该对象实现了指定的接口。
public static Object newProxyInstance(ClassLoader loader, 
                                      Class<?>[] interfaces, 
                                      InvocationHandler h)

【与反射机制的关系】

Proxy.newProxyInstance 方法与 Java 的反射机制密切相关。以下是它们之间的关系:

1、动态方法调用:在 InvocationHandler 的 invoke 方法中,使用反射来调用目标对象的方法。具体来说,Method.invoke(Object obj, Object... args) 方法是反射的一部分,它允许在运行时调用对象的方法。

2、接口的动态实现:通过 Proxy.newProxyInstance 创建的代理对象在运行时动态实现了指定的接口,而不是在编译时确定。这种动态特性是反射机制的一个重要应用。

3、方法信息:在 invoke 方法中,可以通过 Method 对象获取方法的元数据(如方法名、参数类型等),这也是反射机制的一部分。


CGLIB 动态代理

参考示例代码,如下所示:

java 复制代码
public class UserService {
    public void addUser(String username) {
        System.out.println("User " + username + " added.");
    }
}

public class CglibProxy implements MethodInterceptor {

    public Object createProxy(Class<?> clazz) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(clazz);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("Before method: " + method.getName());
        Object result = proxy.invokeSuper(obj, args); // 调用父类的方法
        System.out.println("After method: " + method.getName());
        return result;
    }
}


public class Client2 {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();
        UserService userServiceProxy = (UserService) cglibProxy.createProxy(UserService.class);
        userServiceProxy.addUser("John Doe");
    }
}

JDK17需要添加如下VM选项:

--add-opens java.base/java.lang=ALL-UNNAMED


SpringBoot 与动态代理

Spring Boot 默认情况下使用 JDK 动态代理,除非满足以下条件才会使用 CGLib 动态代理:

1、目标类不是接口:

JDK 动态代理只能代理接口,如果目标类不是接口,Spring Boot 会使用 CGLib 动态代理。

2、proxyTargetClass 属性设置为 true:

在 @EnableAspectJAutoProxy 注解中,可以设置 proxyTargetClass 属性为 true,强制使用 CGLib 动态代理。

为什么 Spring Boot 默认使用 JDK 动态代理?

性能: JDK 动态代理通常比 CGLib 动态代理性能更高,因为它不需要生成额外的字节码。

简洁: JDK 动态代理更简洁,因为它只需要代理接口,而 CGLib 动态代理需要代理类。

为什在某些情况下使用 CGLib 动态代理?

代理非接口类: JDK 动态代理无法代理非接口类,而 CGLib 动态代理可以。

需要更强大的功能: CGLib 动态代理提供了更强大的功能,例如:可以代理私有方法。

总结:

Spring Boot 默认使用 JDK 动态代理,因为它性能更高、更简洁。但在某些情况下,例如代理非接口类或需要更强大的功能时,Spring Boot 会使用 CGLib 动态代理。


总结陈词

💗 后续会逐步分享企业实际开发中的实战经验,有需要交流的可以联系博主。

相关推荐
骑着蜗牛撵大象3276 分钟前
大模型 Agent 的沙箱护栏:gVisor 隔离、出口白名单与审计闭环
java·开发语言·jvm·安全·agent沙箱·gvisor·microvm
独泪了无痕10 分钟前
Hutool之LocalDateTimeUtil:让时间间隔操作如虎添翼
java
三无少女指南2 小时前
从这里开始:Java 后端与 AI 工程的三条阅读路线
java·学习路线·ai工程
ZhengEnCi2 小时前
Agent 规则管理:让 Memory 做索引、让 Skill 存规则——上下文上限实测与分工方案
后端
香蕉大香蕉(06敬畏市场)2 小时前
MySQL 单点详解(07)
java·数据库·mysql·ai·博客·八股
nice_lcj5202 小时前
深入理解 Java 反射:从 Class 对象到方法调用(含性能实测 + 迷你 IOC 实战 + 面试考点)
java·开发语言·后端·面试
骑着蜗牛撵大象3273 小时前
2026 后端 AI 工程化:Spring AI 管调用、MCP 管工具、Agent 内嵌管自治
java·人工智能·后端·spring·ai·可观测性·mcp
tryxr3 小时前
JVM 垃圾回收机制
java·jvm·gc·垃圾回收
摸爬滚打的小李3 小时前
Spring XML + WAR + Tomcat 项目启动
后端
揣一尾游鱼yu4 小时前
Spring上(IOC)笔记
java·笔记·spring·ioc