Skip to content

Spring AOP 的能力与目标 (Spring AOP Capabilities and Goals)

Spring AOP 是用纯 Java 实现的。不需要特殊的编译过程。Spring AOP 也不需要控制类加载器层次结构,因此适用于 Servlet 容器或应用服务器。

目前,Spring AOP 仅支持 方法执行连接点(即对 Spring Bean 上的方法执行进行通知)。虽然可以在不破坏核心 Spring AOP API 的情况下添加对字段拦截的支持,但 Spring AOP 并没有实现字段拦截。如果你需要通知字段访问或更新连接点,请考虑使用 AspectJ 等语言。

Spring AOP 的方法论与大多数其他 AOP 框架不同。其目的不是提供最完整的 AOP 实现(尽管 Spring AOP 的能力已经相当强了),而是提供 AOP 实现与 Spring IoC 之间的紧密集成,以帮助解决企业级应用中的常见问题。

例如,Spring 框架的 AOP 功能通常与 Spring IoC 容器配合使用。切面是使用常规的 Bean 定义语法配置的(尽管这允许强大的“自动代理”功能)。这是与其他 AOP 实现的关键区别。使用 Spring AOP,你无法轻松或高效地完成某些事情,例如通知非常细粒度的对象(通常是领域对象);在这种情况下,AspectJ 是最佳选择。然而,我们的经验表明,Spring AOP 为企业级 Java 应用中大多数适合 AOP 处理的问题提供了一个极佳的解决方案。

Spring AOP 从不试图与 AspectJ 竞争以提供全面的 AOP 解决方案。我们认为,像 Spring AOP 这样基于代理的框架和像 AspectJ 这样功能完备的框架都是有价值的,它们是互补的,而不是竞争关系。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,使得所有 AOP 的使用都能在一个始终如一的基于 Spring 的应用架构中得到实现。这种集成不会影响 Spring AOP API 或 AOP Alliance API,Spring AOP 保持向下兼容。有关 Spring AOP API 的讨论,请参阅下一章

非侵入性原则

Spring 框架的核心原则之一是 非侵入性(Non-invasiveness)。这意味着你不应该被强制在业务或领域模型中引入特定于框架的类和接口。

然而,在某些地方,Spring 框架确实给了你将特定于 Spring 的依赖引入代码库的选择。之所以提供这些选择,是因为在某些场景下,用这种方式阅读或编写特定功能逻辑可能会更简单。不过,Spring 框架(几乎)总是会给你选择:你有权根据最适合你特定用例或场景的方案做出明智的决定。

与本章相关的一个选择就是:选择哪种 AOP 框架(以及哪种 AOP 风格)。你可以选择 AspectJ、Spring AOP,或者两者结合。你还可以选择使用 @AspectJ 注解风格或 Spring XML 配置风格。本章选择先介绍 @AspectJ 风格,这并不代表 Spring 团队更偏好注解风格而非 XML 配置风格。

有关各风格优缺点的完整讨论,请参阅选择哪种 AOP 声明风格


补充教学

1. 务实主义:为什么只支持方法拦截?

很多开发者会问:为什么 Spring AOP 不能拦截变量修改(Field Access)?

  • 企业开发的核心:在 99% 的 Web 应用中,业务逻辑都封装在 Service 层的方法里。拦截了方法,就等于拦截了整个业务单元(例如:开启事务、校验权限)。
  • 代理的局限:Spring AOP 默认通过 Java 动态代理实现,而代理只能拦截方法。如果要支持字段拦截,就必须在字节码层面进行“硬核”修改。Spring 团队认为,为了那 1% 的极端需求而大幅增加框架复杂度是不划算的。

2. Spring AOP vs. AspectJ:该怎么选?

这是面试的高频考点,也是实战中的必选题:

特性Spring AOPAspectJ
织入时机运行时 (Runtime)编译时、类加载时 (Compile/Load-time)
性能略低(需要通过代理跳转)极高(直接修改代码)
拦截范围仅限非 private 方法(Spring Bean)任何东西(构造函数、私有方法、字段等)
复杂度低(引入 AOP Starter 即可)高(需要特殊编译器或 Agent)

选型公式

  • 一般业务(事务、普通日志):Spring AOP。
  • 性能极敏感(高频算法拦截)/ 需要拦截非 Bean 对象 / 拦截私有方法:AspectJ。

3. 理解“非侵入性”的真正含义

“非侵入”不代表代码里绝对没有 org.springframework 包。

  • 低耦合:当你使用 @Transactional 注解时,你的业务逻辑与事务代码是解耦的。即使以后不用 Spring,删除这些注解,你的业务逻辑代码(POJO)依然是可以单独测试和运行的。
  • 可插拔:你可以随时通过配置关闭某个切面,而不需要修改一行 Java 代码。这才是 Spring 追求的真谛。

4. 隐式前提:对象必须是 Spring Bean

这是新手最常踩的坑:如果你通过 new MyService() 自己创建了一个对象,Spring AOP 的切面绝不会生效。

  • 只有从 Spring 容器中 getBean() 拿到的代理对象,才具备 AOP 的能力。因为 Spring AOP 是基于代理的,如果你绕过了容器,就等于绕过了代理。

5. 什么时候用两者结合?

Spring 提供了一种方案:使用 LTW (Load-Time Weaving)。这通常在需要用到 AspectJ 强大拦截能力,但又希望对象能享受 Spring IoC(自动注入)时使用。这种结合在复杂的领域驱动设计(DDD)中比较常见。

Based on Spring Framework.