欢迎光临
我们一直在努力

Java 基础之动态代理

摘要:

        本文从反射机制出发,系统梳理了 Java 动态代理的完整知识体系。文章首先解释了为什么讲完反射要讲动态代理,随后介绍了代理模式的基础概念与三个核心角色,并对比了静态代理与动态代理的差异。在此基础上,深入剖析了 JDK 动态代理与 CGLIB 动态代理两种主流实现,从实现原理、依赖要求、目标对象要求、方法调用方式和性能表现等多个维度进行对比,并说明了 Spring 框架如何根据目标对象是否实现接口来自动选择代理方式.

目录

一、衔接反射:为什么讲完反射要讲动态代理

1.1 什么是动态代理

1.2 为什么讲完反射就要讲动态代理的原因

二、代理模式基础

2.1 代理模式的核心思想

2.2 代理模式的三个核心角色

2.3 代理模式的分类

三、动态代理总览

3.1 动态代理的核心价值

3.2 动态代理的两条技术路线

四、JDK 动态代理

4.1 定义业务接口

4.2 实现真实主题类

4.3 编写 InvocationHandler 调用处理器

4.4 使用 Proxy.newProxyInstance 创建代理对象并完成调用

五、CGLIB 动态代理

5.1 引入 cglib 依赖

5.2 定义目标类(无需实现接口)

5.3 编写 MethodInterceptor 拦截器

5.4 使用 Enhancer 创建代理对象并完成调用

六、JDK 动态代理 vs CGLIB

七、动态代理与AOP

八、典型应用场景

8.1 Spring AOP 事务管理

8.2 MyBatis 映射器(Mapper)

8.3 RPC 框架远程调用

九、常见面试题

十、总结


引言:

        上一章我们深入探讨了反射机制,但并未展开它与代理之间的关系。本章将以此为切入点,从反射自然过渡到代理,系统梳理代理的实现原理,并对比多种动态代理方案之间的差异.

一、衔接反射:为什么讲完反射要讲动态代理

1.1 什么是动态代理

        反射让我们能够在运行时动态地获取类的信息、调用方法、访问字段,它赋予了 Java 程序极大的灵活性。然而,反射本身只是“看”和“调”的能力,真正让这种能力发挥出强大威力的,是建立在反射之上的动态代理.

        动态代理能够在运行时动态地创建代理类,在不修改原有类代码的前提下,为方法调用增加额外的逻辑,这正是 AOP、Spring 事务管理等核心机制得以实现的基础.

1.2 为什么讲完反射就要讲动态代理的原因

原因在于——动态代理的实现离不开反射.

        JDK 动态代理正是通过反射来调用被代理对象的方法,并借助反射获取接口信息来生成代理类。可以说,反射是动态代理的底层支撑,而动态代理则是反射最典型、最实用的应用场景之一。理解了反射,再来看动态代理,你会更容易理解代理类是如何被创建、如何被调用的.

        此外,动态代理本身也是一个值得深入研究的主题。它分为 JDK 动态代理和 CGLIB 动态代理两种主流实现,二者在实现原理、适用场景和性能表现上各有差异,但是在理解这些之前先简单聊聊代理模式.

二、代理模式基础

2.1 代理模式的核心思想

        代理模式是设计模式中的经典结构型模式,其核心思想是:为某个对象提供一个代理对象,由代理对象来控制对原对象的访问。客户端并不直接操作真实对象,而是通过代理来间接完成调用,从而在不改变真实对象代码的前提下,实现对方法调用的增强、拦截或延迟加载.

        就像一位偶像擅长唱歌跳舞,准备举办演唱会,但并不亲自操办前期筹备工作,于是将这些事务委托给经纪人,由经纪人作为中间代理人全程协助处理,主办方联系经纪人不直接对接偶像.

代理模式

2.2 代理模式的三个核心角色

代理模式通常包含三个角色:抽象主题(Subject)、真实主题(RealSubject)和代理(Proxy).

  • 抽象主题定义了业务方法的统一接口.
  • 真实主题是真正执行业务逻辑的对象.
  • 代理则持有真实主题的引用,并在调用真实方法前后插入额外的处理逻辑.

        这种结构让代理可以在方法调用前后做权限校验、日志记录、性能统计等操作,而真实主题对此完全无感知.

2.3 代理模式的分类

 按照代理类的创建时机和方式,代理模式又可分为静态代理和动态代理.

  • 静态代理在编译期就确定代理类,每个真实类都需要手写一个对应的代理类,代码冗余且难以维护.
  • 动态代理是在运行时动态生成代理类,只需一个代理工厂即可为多个目标对象生成代理,灵活性和复用性都大幅提升.

静态代理与动态代理对比

        理解了静态代理的局限,也就自然理解了为什么需要动态代理.

三、动态代理总览

3.1 动态代理的核心价值

        动态代理的核心价值在于,它把 " 代理逻辑 " 与 " 业务逻辑 " 彻底解耦.

        开发者只需要关注真实对象要做什么,而权限校验、日志记录、事务控制等横切关注点,都可以统一交给代理在方法调用前后代为处理。这种能力让代码更加简洁、职责更加清晰,也让系统在扩展新功能时无需改动已有业务类.

3.2 动态代理的两条技术路线

从实现层面看,动态代理主要分为两条技术路线.

  • 一是基于接口的 JDK 动态代理,它要求目标对象必须实现接口,通过反射在运行时生成代理类.
  • 二是基于继承的 CGLIB 动态代理,它通过生成目标类的子类来实现代理,因此不要求目标类实现接口.

        两条路线各有适用场景,也各有优劣,理解它们的差异,是掌握动态代理的关键一步.

四、JDK 动态代理

        JDK 动态代理是 Java 原生提供的动态代理方案,JDK 动态代理基于接口实现,核心依赖于 java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler 两个类.

        下面通过一个完整的实战示例,演示从定义接口到创建代理并完成调用的全过程.

4.1 定义业务接口

// 业务接口:定义需要被代理的方法
public interface UserService {
void addUser(String name);
String getUser(String id);
}

        接口是 JDK 动态代理的前提,代理对象只能代理接口中声明的方法。这里定义了一个用户服务接口,包含新增用户和查询用户两个方法.

4.2 实现真实主题类

// 业务实现层:真正执行业务逻辑的对象
public class UserServiceImpl implements UserService {
@Override
public void addUser(String name) {
System.out.println("新增用户:" + name);
}

@Override
public String getUser(String id) {
return "用户信息,id=" + id;
}
}

        真实主题类实现了业务接口,负责具体的业务逻辑。代理对象会在调用这些方法前后插入额外处理,而真实类本身无需任何改动.

4.3 编写 InvocationHandler 调用处理器

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 调用处理器:拦截代理对象上的所有方法调用
public class LogInvocationHandler implements InvocationHandler {

// 持有真实主题对象的引用
private final Object target;

public LogInvocationHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前的增强逻辑
System.out.println("调用方法前:记录日志,方法名 = " + method.getName());

// 通过反射调用真实对象的方法
Object result = method.invoke(target, args);

// 方法调用后的增强逻辑
System.out.println("调用方法后:记录日志,方法执行完成");

return result;
}
}

        InvocationHandler 是 JDK 动态代理的核心,所有对代理对象方法的调用都会被转发到这里的 invoke 方法。我们可以在调用真实方法前后加入日志、权限校验、性能统计等增强逻辑.

4.4 使用 Proxy.newProxyInstance 创建代理对象并完成调用

import java.lang.reflect.Proxy;

public class JdkProxyDemo {
public static void main(String[] args) {
// 1. 创建真实主题对象
UserService realService = new UserServiceImpl();

// 2. 创建调用处理器,传入真实对象
LogInvocationHandler handler = new LogInvocationHandler(realService);

// 3. 通过 Proxy.newProxyInstance 创建代理对象
// 参数一:类加载器,用于加载生成的代理类
// 参数二:代理类需要实现的接口数组
// 参数三:调用处理器,所有方法调用都会转发给它
UserService proxy = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(),
new Class[]{UserService.class},
handler
);

// 4. 通过代理对象调用方法,触发增强逻辑
proxy.addUser("张三");
String user = proxy.getUser("1001");
System.out.println(user);
}
}

        运行上述代码,控制台会依次输出方法调用前后的日志,以及真实业务逻辑的执行结果。可以看到,客户端只与代理对象交互,真实对象的方法调用被 InvocationHandler 拦截并增强,整个过程完全不需要修改真实类的代码.

五、CGLIB 动态代理

        CGLIB 动态代理则是另一种主流的动态代理方案,它基于继承实现,通过生成目标类的子类来完成代理,因此不要求目标类实现接口.

        下面通过一个完整的实战示例,演示从引入依赖到创建代理并完成调用的全过程.

5.1 引入 cglib 依赖

<!– Maven 坐标:引入 cglib 依赖 –>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.3.0</version>
</dependency>

        cglib 是一个基于 ASM 字节码操作库的高性能代码生成库,它可以在运行时动态生成目标类的子类。如果使用 Spring Boot 或较新的 Spring 版本,通常已经内置了 cglib,无需额外引入.

5.2 定义目标类(无需实现接口)

// 目标类:普通类,无需实现任何接口
public class UserService {

public void addUser(String name) {
System.out.println("新增用户:" + name);
}

public String getUser(String id) {
return "用户信息,id=" + id;
}
}

        与 JDK 动态代理不同,CGLIB 通过继承目标类来生成代理子类,因此目标类不需要实现接口。但需要注意,目标类不能是 final 类,被代理的方法也不能是 final 方法,否则无法被重写.

5.3 编写 MethodInterceptor 拦截器

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

// 方法拦截器:拦截代理对象上的所有方法调用
public class LogMethodInterceptor implements MethodInterceptor {

@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// 方法调用前的增强逻辑
System.out.println("调用方法前:记录日志,方法名 = " + method.getName());

// 通过 MethodProxy 调用目标类的原始方法
Object result = proxy.invokeSuper(obj, args);

// 方法调用后的增强逻辑
System.out.println("调用方法后:记录日志,方法执行完成");

return result;
}
}

        MethodInterceptor 是 CGLIB 动态代理的核心接口,所有对代理对象方法的调用都会被转发到这里的 intercept 方法。与 JDK 动态代理的 InvocationHandler 不同,这里通过 MethodProxy.invokeSuper 直接调用目标类的原始方法,性能更高.

5.4 使用 Enhancer 创建代理对象并完成调用

import net.sf.cglib.proxy.Enhancer;

public class CglibProxyDemo {
public static void main(String[] args) {
// 1. 创建 Enhancer 对象,用于生成代理类
Enhancer enhancer = new Enhancer();

// 2. 设置父类,即目标类
enhancer.setSuperclass(UserService.class);

// 3. 设置方法拦截器,所有方法调用都会转发给它
enhancer.setCallback(new LogMethodInterceptor());

// 4. 创建代理对象
UserService proxy = (UserService) enhancer.create();

// 5. 通过代理对象调用方法,触发增强逻辑
proxy.addUser("李四");
String user = proxy.getUser("1002");
System.out.println(user);
}
}

运行结果:

调用方法前:记录日志,方法名 = addUser
新增用户:李四
调用方法后:记录日志,方法执行完成
调用方法前:记录日志,方法名 = getUser
调用方法后:记录日志,方法执行完成
用户信息,id=1002

        运行上述代码,控制台会依次输出方法调用前后的日志,以及真实业务逻辑的执行结果。可以看到,客户端只与代理对象交互,目标类的方法调用被 MethodInterceptor 拦截并增强,整个过程完全不需要修改目标类的代码,也不需要目标类实现任何接口.

六、JDK 动态代理 vs CGLIB

        通过前两段的实战演示,我们已经分别掌握了 JDK 动态代理和 CGLIB 动态代理的完整用法。二者虽然都能在运行时动态生成代理类,但在实现原理、适用场景和性能表现上存在明显差异。下面从多个维度对它们进行系统对比.

为了更直观地对比二者的差异,下面用一张表格进行汇总:

对比维度JDK 动态代理CGLIB 动态代理
实现原理 基于接口,通过反射生成代理类 基于继承,通过 ASM 字节码生成子类
依赖要求 Java 原生支持,无需额外依赖 需要引入 cglib 依赖
目标对象要求 必须实现接口 无需实现接口,但类和方法不能是 final
方法调用方式 通过反射调用目标方法 通过 MethodProxy 直接调用原始方法
创建代理速度 较快 较慢(需生成字节码子类)
方法调用性能 相对较慢(反射开销) 相对较快(直接调用)
适用场景 目标对象实现了接口 目标类未实现接口或需要代理具体类
典型应用 Spring 中代理实现了接口的 Bean Spring 中代理未实现接口的 Bean

        在实际开发中,选择哪种动态代理方案,主要取决于目标对象是否实现了接口。如果目标对象实现了接口,优先使用 JDK 动态代理,代码更简洁、更符合面向接口编程的思想;如果目标对象没有实现接口,则只能使用 CGLIB 动态代理。Spring 框架在创建 AOP 代理时,也会根据目标对象是否实现接口来自动选择 JDK 动态代理或 CGLIB 动态代理,这也是我们在日常开发中最常遇到的场景.

七、动态代理与AOP

        动态代理与 AOP(面向切面编程)有着密不可分的关系。AOP 的核心思想是把日志记录、权限校验、事务管理等横切关注点从业务逻辑中抽取出来,统一在方法调用前后进行处理,而动态代理正是实现这种「横切逻辑织入」的关键技术。无论是 JDK 动态代理还是 CGLIB 动态代理,它们都能在运行时拦截方法调用,并在调用前后插入增强逻辑,这与 AOP 的切面思想天然契合.

        在 Spring 框架中,AOP 的实现正是建立在动态代理之上的。当目标对象实现了接口时,Spring 默认使用 JDK 动态代理来创建代理对象;当目标对象没有实现接口时,Spring 则会自动切换到 CGLIB 动态代理,通过生成目标类的子类来完成代理。这种自动选择机制,让开发者无需关心底层代理的创建细节,只需专注于业务逻辑本身.

        从实现角度看,AOP 中的切面(Aspect)、通知(Advice)和切点(Pointcut)最终都会转化为对代理对象方法调用的拦截逻辑。例如,一个标注了 @Transactional 的事务方法,在调用时会被代理对象拦截,代理会在方法执行前开启事务、在方法执行后提交或回滚事务。整个过程对调用方完全透明,这正是动态代理与 AOP 结合带来的强大能力.

动态代理与AOP的结合

八、典型应用场景

        动态代理在 Java 生态中应用极为广泛,几乎贯穿了主流框架的底层实现。下面选取三个最具代表性的场景,看看动态代理是如何在实际项目中发挥作用的.

8.1 Spring AOP 事务管理

        Spring 的声明式事务是动态代理最典型的应用。当我们在 Service 方法上标注 @Transactional 时,Spring 会为这个 Bean 创建代理对象,在方法调用前开启事务、调用后提交或回滚事务。以 JDK 动态代理为例,其核心逻辑如下:

// 事务拦截器:模拟 Spring 事务代理的核心逻辑(JDK代理:使用接口)
public class TransactionInterceptor implements InvocationHandler {

private final Object target;

public TransactionInterceptor(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法调用前:开启事务
System.out.println("开启事务…");
try {
Object result = method.invoke(target, args);
// 方法调用后:提交事务
System.out.println("提交事务…");
return result;
} catch (Exception e) {
// 发生异常:回滚事务
System.out.println("回滚事务…");
throw e;
}
}
}

        开发者只需要声明式地标注注解,事务的开启、提交、回滚全部由代理对象在幕后完成,业务代码保持纯净.

8.2 MyBatis 映射器(Mapper)

        MyBatis 的 Mapper 接口并没有实现类,却能直接调用方法执行 SQL,这正是动态代理的功劳。MyBatis 通过 JDK 动态代理为每个 Mapper 接口生成代理对象,在 invoke 方法中根据方法名解析对应的 SQL 语句并执行。核心逻辑如下:

// Mapper 代理:模拟 MyBatis 根据方法名执行 SQL
public class MapperProxy implements InvocationHandler {

private final SqlSession sqlSession;

public MapperProxy(SqlSession sqlSession) {
this.sqlSession = sqlSession;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 根据方法全限定名找到对应的 SQL 语句
String statementId = method.getDeclaringClass().getName() + "." + method.getName();
// 执行 SQL 并返回结果
return sqlSession.selectOne(statementId, args);
}
}

        正因为有了动态代理,MyBatis 才能让开发者只定义接口、不写实现类,框架在运行时自动完成 SQL 绑定与执行.

8.3 RPC 框架远程调用

        在 Dubbo、Feign 等 RPC 框架中,客户端调用的只是一个本地接口,实际请求却通过网络发送到远程服务。动态代理在这里扮演了 “ 本地代理 + 远程转发 ” 的角色:代理对象拦截本地方法调用,将方法名和参数序列化后通过网络发送给服务端,再把返回结果反序列化回传给调用方。核心逻辑如下:

// RPC 代理:模拟远程调用的本地代理
public class RpcProxy implements InvocationHandler {

private final String serviceAddress;

public RpcProxy(String serviceAddress) {
this.serviceAddress = serviceAddress;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 将方法名和参数封装为请求对象
RpcRequest request = new RpcRequest(method.getName(), args);
// 2. 通过网络发送请求到远程服务
RpcResponse response = RpcClient.send(serviceAddress, request);
// 3. 返回远程执行结果
return response.getResult();
}
}

        对调用方而言,远程调用与本地调用毫无差别,这种 “ 透明化 ” 体验正是动态代理赋予 RPC 框架的核心能力.

九、常见面试题

问题一:JDK 动态代理和 CGLIB 动态代理有什么区别?

答案:

        1、实现原理不同

                JDK 动态代理基于接口,通过反射在运行时生成实现指定接口的代理类.

                CGLIB 基于继承,通过 ASM 字节码技术生成目标类的子类.

        2、目标对象要求不同

                JDK 动态代理要求目标对象必须实现至少一个接口.

                CGLIB 不要求实现接口,但目标类不能是 final 类,被代理的方法也不能是 final 方法.

        3、依赖不同

                JDK 动态代理是 Java 原生支持,无需额外依赖.

                CGLIB 需要引入 cglib 依赖.

        4、方法调用方式不同

                JDK 动态代理通过反射调用目标方法.

                CGLIB 通过 MethodProxy.invokeSuper 直接调用原始方法,性能通常更高.

问题二:Spring 是如何选择 JDK 动态代理和 CGLIB 的?

答案:

        当目标对象实现了接口时,优先使用 JDK 动态代理;当目标对象没有实现接口时,自动切换到 CGLIB 动态代理。然而,从 Spring Boot 2.x 开始,Spring 默认强制使用 CGLIB 动态代理,即使目标类实现了接口也是如此,这样能避免某些场景下 JDK 代理因类型转换失败而引发的问题。开发者也可以通过配置项 spring.aop.proxy-target-class=false 来恢复使用 JDK 动态代理.

问题三:动态代理存在哪些性能问题?如何优化?

答案:

动态代理的性能问题主要集中在两个方面:

        代理对象创建阶段:CGLIB 需要生成字节码子类,创建速度略慢于 JDK 动态代理,但代理对象通常只创建一次,影响有限.

        方法调用阶段:JDK 动态代理用反射调用目标方法,存在反射开销,但随着 JVM 反射优化的不断改进,这个差距正在缩小.

优化手段包括:

        优先使用 CGLIB 以规避反射调用.

        在 Spring 中合理使用 AOP 切点,避免对高频方法做无谓的代理拦截.

        对于热点方法,可以考虑在编译期通过 AspectJ 等静态织入方式替代运行时动态代理.

问题四:为什么 JDK 动态代理只能代理接口,不能代理类?

答案:

        因为 JDK 的代理类是通过 Proxy.newProxyInstance 在运行时创建的,这个代理类本身继承了 java.lang.reflect.Proxy 类。然而 Java 是单继承机制,代理类已经继承了 Proxy,就无法再继承其他类,因此只能通过实现接口的方式来扩展能力。这也是 JDK 动态代理要求目标对象必须实现接口的根本原因。而 CGLIB 通过生成目标类的子类来实现代理,不占用继承名额,所以可以代理普通类.

问题五:动态代理和静态代理有什么区别?实际项目中如何选择?

答案:

        静态代理在编译期就确定代理类,每个真实类都需要手写一个对应的代理类,代码冗余、难以维护,适合代理逻辑固定、目标类数量少的场景.

        动态代理在运行时动态生成代理类,一个代理工厂即可为多个目标对象生成代理,灵活性和复用性大幅提升,适合代理逻辑通用、目标类数量多的场景.

        在实际项目中,绝大多数场景都应优先使用动态代理,尤其是需要为大量业务类统一添加日志、事务、权限等横切逻辑时;只有代理逻辑极其简单且目标类极少时,才考虑静态代理.

十、总结

本文核心要点如下:

  • 动态代理与反射的关系:反射是动态代理的底层支撑,动态代理是反射最典型的应用场景.
  • 两条路线:JDK 动态代理基于接口、通过反射生成代理类

                         CGLIB 动态代理基于继承、通过 ASM 字节码生成子类.

  • 选择依据:目标对象实现了接口优先用 JDK 动态代理,未实现接口则用 CGLIB;

                         Spring 会根据目标类是否实现接口自动选择.

  • 典型应用:Spring AOP 事务管理、MyBatis Mapper、RPC 远程调用都建立在动态代理之上.

        掌握动态代理,是理解 Spring 等主流框架底层机制的关键一步,相信大家一定能行.

赞(0)
未经允许不得转载:171主机测评 » Java 基础之动态代理
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址