在 Spring 框架中,AOP 模块是核心组件之一,它实现了诸如应用程序审计、事务管理和安全等重要功能。
AOP 被称为实现横切关注点的工具。术语横切关注点指的是应用程序中无法从其余应用程序分解的逻辑,并且可能导致代码重复和紧密耦合。通过使用 AOP 对独立的逻辑块(称为关注点)进行模块化,可以将它们应用于应用程序的许多部分,而无需重复代码或创建硬依赖关系。
日志记录和安全是存在于许多应用程序中的典型横切关注点。您通常需要在程序的许多部分记录操作或检查某人是否具有权限。AOP 帮助您在不重复编写相同代码的情况下完成此操作。
1. 什么是 Spring AOP?
面向切面编程是一种补充面向对象编程 (OOP) 的范式。虽然 OOP 关注将代码组织成类和对象,但 AOP 专注于横切关注点——影响应用程序多个部分的功能。横切关注点包括日志记录、安全、事务等。
在 Spring 框架中,Spring AOP 是一个核心模块,它提供了一个强大而优雅的解决方案来解决横切关注点带来的挑战。它允许我们对这些关注点进行模块化,从而提高代码的可维护性并减少冗余。Spring AOP 与 Spring IoC (控制反转) 容器无缝集成,使其成为 Spring 生态系统不可或缺的一部分。
2. Spring AOP 架构
Spring AOP 的核心架构基于代理。当应用程序初始化时,一个类的建议实例是通过ProxyFactory 创建该类的代理实例并将所有方面编织到代理中而创建的。
在运行时,Spring 会分析ApplicationContext 中定义的横切关注点,并动态生成代理 bean(它们包装了底层的目标 bean)。调用者不是直接访问目标 bean,而是注入了代理 bean。

在内部,Spring 有两种代理实现
- JDK 动态代理:当要建议的目标对象实现接口时。
- CGLIB 代理:当建议的目标对象不实现接口时。例如,它是一个具体类。
请注意,JDK 动态代理仅支持接口的代理。
请记住,Spring AOP 存在一些限制。例如
- 由于无法扩展,因此无法代理最终类或方法。
- 此外,由于代理实现,Spring AOP 仅适用于 Spring Bean 上的公共非静态方法。
- 如果在同一类中从一个方法到另一个方法的内部方法调用,则不会为内部方法调用执行建议。
3. 什么是 Advice、Joinpoint 和 Pointcut?
- Joinpoint:是程序执行的点,例如执行方法或处理异常。在 Spring AOP 中,joinpoint 始终代表方法执行。 Joinpoint 定义了您可以在应用程序中插入其他逻辑的位置,使用 AOP。
- Advice:是在特定 joinpoint 执行的代码。有许多类型的 advice,例如before,它在 joinpoint 之前执行,以及after,它在 joinpoint 之后执行。
- Aspect:是在一个类中封装的 advice 和 pointcut 的组合。
- Pointcut:是匹配 joinpoint 的谓词或表达式。
- Weaving:是将方面插入到应用程序代码的适当位置的过程。AspectJ 支持一种称为loadtime weaving (LTW) 的 weaving 机制,它拦截底层的 JVM 类加载器并在类加载器加载字节码时提供 weaving。
- Target:是由 AOP 过程修改其执行流程的对象。通常您会看到目标对象被称为advised object。
- Spring 默认使用 AspectJ pointcut 表达式语言。

4. Spring AOP 中的 Advice 类型
Spring AOP 中有五种类型的 advice。
- Before advice:在 join point 之前执行的 advice,但无法阻止执行流程继续到 join point(除非它抛出异常)。
- After returning advice:在 join point 正常完成后执行的 advice:例如,如果方法返回而没有抛出异常。
- After throwing advice:如果方法通过抛出异常退出时执行的 advice。
- After advice:无论 join point 如何退出(正常或异常返回),都将执行的 advice。
- Around advice:围绕 join point(例如方法调用)的 advice。这是功能最强大的 advice 类型。Around advice 可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到 join point 还是通过返回自己的返回值或抛出异常来短路建议的方法执行。
选择 advice 类型基于应用程序的要求,但我们应为我们的需求选择最具体的 advice 类型。当 before advice 就能满足需求时,不要使用 around advice。
在大多数情况下,围绕(around)类型的Advice可以完成其他三种Advice类型所能完成的一切,但对于您尝试实现的目标来说,它可能过于复杂。 通过尽可能地将Advice类型保持专注,我们可以减少出错的范围。
5. Spring AOP 入门
让我们看看如何在 Spring 应用程序中使用 AOP。
5.1. Maven
在编写任何代码之前,您需要将Spring AOP 依赖项导入到您的项目中。
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>6.1.1</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.1.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.20.1</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.20.1</version>
</dependency>
在 Spring Boot 应用程序中,添加依赖项会容易得多。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
5.2. 启用 Spring AOP
我们可以使用配置上的 @EnableAspectJAutoProxy 注解来 启用 Spring AOP。
@Configuration
@EnableAspectJAutoProxy
@ComponentScan
public class AopConfig {
}
5.3. 使用 Aspect 和 Pointcut 表达式定义 Advice
每个 Aspect 类都应该使用 @Aspect 注解进行注解。 在该类中,我们然后指定 Pointcut 和 Advice。 它也应该使用 @Component 进行注解,以便通过组件扫描(或者以另一种方式配置为 Spring bean)被拾取。
@Aspect
public class EmployeeCRUDAspect {
@Before("execution(* EmployeeManager.getEmployeeById(..))") //point-cut expression
public void logBeforeV1(JoinPoint joinPoint) {
System.out.println("EmployeeCRUDAspect.logBeforeV1() : " + joinPoint.getSignature().getName());
}
}
5.4. 受 Advice 影响的方法(连接点)
编写您想要执行 Advice 的方法,并且这些方法与 Pointcut 表达式匹配。
@Component
public class EmployeeManager {
public EmployeeDTO getEmployeeById(Integer employeeId) {
System.out.println("Method getEmployeeById() called");
return new EmployeeDTO();
}
}
在上面的例子中,logBeforeV1() 将在 之前 执行 getEmployeeById() 方法,因为它匹配了连接点表达式。
5.5. 运行应用程序
运行应用程序并观察控制台。
public class TestAOP
{
@SuppressWarnings("resource")
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext
("com/howtodoinjava/demo/aop/applicationContext.xml");
EmployeeManager manager = context.getBean(EmployeeManager.class);
manager.getEmployeeById(1);
}
}
程序输出
EmployeeCRUDAspect.logBeforeV1() : getEmployeeById
Method getEmployeeById() called
Spring aop 教程,面向初学者,附带示例。
6. Spring AOP 注解示例
- Spring AOP AspectJ 注解配置示例:学习如何使用 AspectJ 注解配置来配置 AOP Aspect
- Spring AOP AspectJ @Before:学习如何使用
@Before注解来配置 advice aspect。 - Spring AOP AspectJ @After:学习如何使用
@After注解来配置 advice aspect。 - Spring AOP AspectJ @Around:学习如何使用
@Around注解来配置 advice aspect。 - Spring AOP AspectJ @AfterReturning:学习如何使用
@AfterReturning注解来配置 advice aspect。 - Spring AOP AspectJ @AfterThrowing:学习如何使用
@AfterThrowing注解来配置 advice aspect。
7. Spring AOP 高级教程
- Spring AOP Aspects Ordering:学习如何在多个需要以特定顺序执行的 Aspect 时,对 Aspect 执行进行排序。
- Spring AOP AspectJ Pointcut 表达式:学习编写 Pointcut 表达式以匹配各种连接点。
8. Spring AOP 面试题
- Spring AOP 面试题及答案:一些 Java 面试中经常被问到的 Spring AOP 面试题。
祝您学习愉快!!
并且此示例中还有一个问题,您应该在项目中添加配置类
@Configuration
@EnableAspectJAutoProxy
public class Config {
}
我花了整整一天的时间才让这段代码正常工作。 请使用以下代码修复此代码
@Aspect
@Component
public class EmployeeCRUDAspect {
而不是
@Aspect
public class EmployeeCRUDAspect {
为什么使用
@Component注解?@Aspect会被 Spring 自动检测。[参考]也许,因为我使用了注解配置而不是 XML 配置