AIGC标识 一图搞懂AOP、动态代理和反射之间的关系

前言

在 Java 基础学习中,很多初学者容易混淆 AOP、代理模式和反射之间的关系。它们经常一起出现,但实际上属于不同层次的概念。

在讨论之前,需要先明确:

AOP ≠ Spring AOP ≠ JDK 动态代理。

  • AOP 是一种编程思想,用于将日志、事务、权限校验等横切逻辑与业务逻辑分离。
  • Spring AOP 是 Spring 对 AOP 思想的一种实现。
  • 动态代理 是 Spring AOP 在运行期实现方法增强的重要手段。
  • 反射 是 Java 提供的一种运行时能力,可以被动态代理用于调用目标方法。

下面以 Spring AOP 为例,梳理它们之间的关系。

该图片较全面地总结了 AOP、代理和反射之间的关系(如有错误,欢迎指出):

AOP-代理-反射关系图

概念

AOP(面向切面编程)

AOP 的全称是 Aspect-Oriented Programming,即面向切面编程。

传统的面向对象编程通常按照类和对象组织业务代码,而 AOP 关注的是散布在多个类中的公共逻辑,例如:

  • 日志记录
  • 事务管理
  • 权限校验
  • 性能统计
  • 异常处理
  • 接口限流

这些逻辑往往会跨越多个业务模块,因此也被称为横切关注点。

解决的问题

假设系统中有转账、下单和退款三个业务方法,它们都需要记录日志并进行权限校验:

public void transfer() {
    checkPermission();
    writeLog();

    // 转账业务
}

public void createOrder() {
    checkPermission();
    writeLog();

    // 下单业务
}

public void refund() {
    checkPermission();
    writeLog();

    // 退款业务
}

如果直接在每个方法中编写这些公共逻辑,会出现以下问题:

  1. 公共代码散落在不同业务类中。
  2. 业务逻辑与非业务逻辑耦合。
  3. 修改公共逻辑时,需要同时修改多个位置。
  4. 容易产生重复代码或遗漏。

AOP 的核心目标就是:

将横切关注点从业务代码中抽离,并在指定位置统一织入。

经过分离以后,业务方法只需要关注自身职责:

public void transfer() {
    // 转账业务
}

日志、事务等逻辑则由 AOP 在方法执行前后统一处理。

常见的实现方式

Java 生态中常见的 AOP 实现包括 Spring AOP 和 AspectJ。

Spring AOP

Spring AOP 主要通过运行时动态代理实现。

Spring 在容器中创建 Bean 时,会判断该 Bean 是否需要被增强。如果需要,Spring 会为其创建一个代理对象。调用者实际拿到的通常是代理对象,而不是未经增强的原始对象。

调用过程可以简化为:

调用者
  ↓
代理对象
  ↓
执行切面逻辑
  ↓
目标对象

例如:

调用转账方法
    ↓
开启事务
    ↓
执行转账业务
    ↓
提交事务

因此,Spring AOP 通常属于运行时织入。

AspectJ

AspectJ 是一套更加完整的 AOP 实现。它可以直接修改或增强类的字节码,不局限于通过代理对象拦截方法调用。

AspectJ 支持的织入方式包括:

  • 编译期织入
  • 编译后织入
  • 类加载期织入

与 Spring AOP 相比,AspectJ 能够支持更丰富的连接点,例如构造方法调用、字段访问等,但配置和使用成本也相对更高。

需要注意的是:

Spring AOP 和 AspectJ 不是同一个概念。Spring 可以集成 AspectJ,但 Spring AOP 本身主要基于代理实现。

Spring AOP 的核心概念

切面(Aspect)

切面是对横切逻辑的封装,例如日志切面、事务切面和权限校验切面。

@Aspect
@Component
public class LogAspect {
}

连接点(Join Point)

连接点表示程序执行过程中可以被增强的位置。在 Spring AOP 中,连接点主要是方法执行。

切入点(Pointcut)

切入点用于筛选需要被增强的连接点。例如,下面的表达式表示匹配 service 包中的所有方法:

@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceMethods() {
}

可以简单理解为:

  • 连接点:所有可能被增强的方法。
  • 切入点:从中选出实际需要增强的方法。

通知(Advice)

通知是切面在特定时间执行的具体逻辑。Spring AOP 常见的通知类型包括:

  • @Before:目标方法执行前
  • @After:目标方法结束后
  • @AfterReturning:目标方法正常返回后
  • @AfterThrowing:目标方法抛出异常后
  • @Around:环绕目标方法执行

例如:

@Before("serviceMethods()")
public void before() {
    System.out.println("方法执行前记录日志");
}

目标对象(Target)

目标对象是实际包含业务逻辑的对象,也就是被代理、被增强的对象。

代理对象(Proxy)

代理对象由 Spring 创建。调用者通过代理对象间接访问目标对象,代理对象负责执行切面逻辑和目标方法。

织入(Weaving)

织入是将切面逻辑应用到目标对象的过程。Spring AOP 主要在运行时通过创建代理对象完成织入。

引介(Introduction)

引介允许在不修改原类代码的情况下,为目标对象增加新的接口及其实现。这个功能在普通业务开发中使用得相对较少,但也是 Spring AOP 概念体系的一部分。

代理模式

代理模式是一种结构型设计模式。在代理模式中,调用者不会直接访问目标对象,而是通过代理对象间接访问目标对象。

调用者 → 代理对象 → 目标对象

代理对象可以在调用目标对象之前或之后加入额外逻辑。

解决的问题

代理模式主要解决的是:

在不修改目标类核心代码的情况下,控制或增强对目标对象的访问。

常见应用包括:

  • 方法调用前后记录日志
  • 权限校验
  • 事务控制
  • 远程调用
  • 延迟加载
  • 缓存
  • 性能监控

代理模式本身不等于 AOP。AOP 是一种编程思想,而代理模式是一种设计模式。只是代理模式提供的“调用前后增强”能力,非常适合用来实现 AOP。

静态代理

静态代理是指代理类由开发者提前编写,在编译时代理关系就已经确定。

首先定义接口:

public interface UserService {
    void saveUser();
}

目标类实现接口:

public class UserServiceImpl implements UserService {

    @Override
    public void saveUser() {
        System.out.println("保存用户");
    }
}

编写代理类:

public class UserServiceProxy implements UserService {

    private final UserService target;

    public UserServiceProxy(UserService target) {
        this.target = target;
    }

    @Override
    public void saveUser() {
        System.out.println("记录开始日志");
        target.saveUser();
        System.out.println("记录结束日志");
    }
}

使用代理对象:

public class Main {

    public static void main(String[] args) {
        UserService target = new UserServiceImpl();
        UserService proxy = new UserServiceProxy(target);

        proxy.saveUser();
    }
}

运行结果:

记录开始日志
保存用户
记录结束日志

静态代理的优点是结构直观、容易理解,而且不依赖复杂的运行时机制。但它也存在明显缺点:

  • 每个目标类型通常都需要编写对应的代理类。
  • 接口中的方法发生变化时,代理类也要跟着修改。
  • 目标类数量较多时,会产生大量重复代码。

动态代理

动态代理不需要开发者为每个目标类手动编写代理类,而是在程序运行过程中动态生成代理对象。

Java 中常见的动态代理方式包括:

  • JDK 动态代理
  • CGLIB 动态代理

JDK 动态代理

JDK 动态代理面向接口创建代理对象,因此目标类通常需要实现接口。

UserService target = new UserServiceImpl();

UserService proxy = (UserService) Proxy.newProxyInstance(
        target.getClass().getClassLoader(),
        target.getClass().getInterfaces(),
        (proxyObject, method, args) -> {
            System.out.println("记录开始日志");

            Object result = method.invoke(target, args);

            System.out.println("记录结束日志");
            return result;
        }
);

proxy.saveUser();

Proxy.newProxyInstance() 需要三个主要参数:

Proxy.newProxyInstance(
    ClassLoader loader,
    Class<?>[] interfaces,
    InvocationHandler handler
)

它们分别表示:

  • loader:用于加载代理类的类加载器。
  • interfaces:代理对象需要实现的接口。
  • handler:代理对象调用方法时执行的处理逻辑。

当调用 proxy.saveUser() 时,代理对象会将调用转交给 InvocationHandler 的 invoke() 方法。invoke() 可以在目标方法执行前后增加额外逻辑,并通过下面的代码调用目标方法:

method.invoke(target, args);

这里的 Method.invoke() 就属于反射调用。

需要注意避免写成:

method.invoke(proxyObject, args);

因为这相当于再次调用代理对象,可能不断进入 InvocationHandler,最终导致递归调用或栈溢出。

CGLIB 动态代理

CGLIB 主要通过继承目标类、生成目标类子类的方式创建代理对象,因此目标类不一定需要实现接口。

Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(UserServiceImpl.class);

enhancer.setCallback((MethodInterceptor) (
        proxyObject,
        method,
        args,
        methodProxy
) -> {
    System.out.println("记录开始日志");

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

    System.out.println("记录结束日志");
    return result;
});

UserServiceImpl proxy = (UserServiceImpl) enhancer.create();
proxy.saveUser();

CGLIB 代理的核心思路可以简化为:

class UserServiceProxy extends UserServiceImpl {

    @Override
    public void saveUser() {
        // 增强逻辑
        super.saveUser();
        // 增强逻辑
    }
}

实际的代理类由框架在运行时动态生成,不需要开发者手动编写。

由于 CGLIB 依赖继承实现代理,因此存在以下限制:

  • final 类不能被继承,所以不能通过这种方式创建代理子类。
  • final 方法不能被重写,所以无法被代理对象增强。
  • private 方法对子类不可见,也无法通过重写进行增强。

两种动态代理的适用场景

对比项 JDK 动态代理 CGLIB 动态代理
主要实现方式 实现目标接口 继承目标类
是否要求接口 通常需要 不需要
代理对象类型 接口的实现类 目标类的子类
主要限制 主要代理接口方法 不能重写 final、private 方法
方法调用 常见方式是反射调用 常见方式是生成字节码并调用父类方法

对于 Spring AOP,可以先作如下理解:

  • 适合使用接口代理时,可以采用 JDK 动态代理。
  • 需要基于具体类代理时,可以采用 CGLIB。
  • 具体使用哪一种方式,还会受到 Spring 配置、目标类结构及框架版本的影响。

还需要注意,虽然 Spring AOP 使用动态代理,但并不意味着 Spring AOP 的所有方法调用都会被增强。

例如,同一个类中通过 this 调用另一个方法:

@Service
public class OrderService {

    public void createOrder() {
        this.pay();
    }

    @Transactional
    public void pay() {
        // 支付逻辑
    }
}

createOrder() 内部直接调用 this.pay(),没有经过 Spring 创建的代理对象,因此 pay() 上的事务增强可能不会生效。这也是 Spring AOP 基于代理实现所带来的典型限制。

反射

Java 反射是指程序在运行过程中动态获取类的信息,并操作类、对象、方法和字段的能力。

反射相关的核心类型包括:

  • Class
  • Method
  • Field
  • Constructor

解决的问题

正常调用一个方法时,通常需要在编译阶段明确对象类型和方法名称:

UserService userService = new UserServiceImpl();
userService.saveUser();

但框架往往无法提前知道用户会定义哪些类、哪些方法以及哪些注解。

反射可以让程序在运行时完成这些操作:

  • 获取类的信息
  • 获取类中的方法和字段
  • 检查类、方法或字段上的注解
  • 动态创建对象
  • 动态读取或修改字段
  • 动态调用方法

例如,运行时获取类的信息:

Class<?> clazz = UserServiceImpl.class;

System.out.println(clazz.getName());

for (Method method : clazz.getDeclaredMethods()) {
    System.out.println(method.getName());
}

通过反射创建对象:

Class<?> clazz = UserServiceImpl.class;

Object object = clazz
        .getDeclaredConstructor()
        .newInstance();

通过反射调用方法:

Method method = clazz.getDeclaredMethod("saveUser");
method.invoke(object);

反射的主要价值在于:

让程序具有运行时检查和操作未知类型的能力,从而支撑框架的通用化设计。

Spring 可以利用反射完成许多工作,例如:

  • 扫描和识别注解
  • 分析 Bean 的类型信息
  • 查找构造方法
  • 完成依赖注入
  • 读取 AOP 切点相关信息
  • 调用目标方法

但需要注意:

动态代理不等于反射,反射也不等于动态代理。

JDK 动态代理的 InvocationHandler 经常通过 Method.invoke() 调用目标对象,因此会直接使用反射;CGLIB 的核心则是运行时字节码生成和方法拦截,不应简单概括为“完全依靠反射”。

三者之间的关系

AOP、代理模式和反射可以从三个不同层次理解:

概念 所属层次 主要作用
AOP 编程思想 分离横切关注点与业务逻辑
代理模式 设计模式 通过代理对象控制或增强目标对象
反射 Java 运行时机制 动态获取类型信息并操作对象或方法

以 Spring AOP 使用 JDK 动态代理为例,三者之间的关系如下:

AOP
│
│ 需要实现横切逻辑的统一织入
↓
Spring AOP
│
│ 使用代理对象拦截方法调用
↓
JDK 动态代理
│
│ InvocationHandler 接收方法调用
↓
反射调用 Method.invoke()
│
↓
目标对象的业务方法

一次完整的方法调用过程大致如下:

调用者调用代理对象的方法
            ↓
代理对象将调用交给 InvocationHandler
            ↓
执行日志、事务、权限校验等增强逻辑
            ↓
通过反射调用目标对象的方法
            ↓
获得目标方法的返回值
            ↓
执行方法调用后的增强逻辑
            ↓
将结果返回给调用者

如果 Spring AOP 使用 CGLIB,调用链则可以简化为:

调用者
  ↓
CGLIB 生成的代理子类
  ↓
方法拦截器
  ↓
切面逻辑
  ↓
目标方法

因此,“AOP 的底层就是反射”这种说法并不严谨。

更准确的说法是:

Spring AOP 是 AOP 思想的一种实现,它主要借助动态代理拦截方法调用;JDK 动态代理的目标方法调用通常会使用反射,而 CGLIB 主要依赖运行时字节码生成和方法拦截。

总结

AOP、代理模式和反射虽然经常一起出现,但它们并不是同一个概念。

  • AOP 是一种编程思想,关注横切逻辑与业务逻辑的分离。
  • Spring AOP 是 AOP 思想的一种实现,主要通过运行时动态代理完成方法增强。
  • 代理模式是一种设计模式,通过代理对象间接访问并增强目标对象。
  • JDK 动态代理主要面向接口,通常通过 InvocationHandler 和反射调用目标方法。
  • CGLIB 动态代理主要面向具体类,通过生成目标类的子类实现方法拦截。
  • 反射是 Java 的运行时能力,可以动态获取类型信息、创建对象和调用方法。
  • AspectJ 可以直接进行字节码织入,不依赖 Spring AOP 的代理调用模型。

最终可以用一句话概括三者的关系:

AOP 提出了“为什么增强”,代理模式解决“如何拦截并增强”,反射则可以为运行时识别和调用方法提供底层能力。

posted @ 2026-08-09 19:34  Keen6509  阅读(13)  评论(0)    收藏  举报