一、先明确核心概念(快速回顾)
在动手前,先理清 Spring AOP 的核心术语,避免后续代码理解混乱:
- 切面(Aspect):封装 “横切逻辑” 的类(比如日志、权限校验、事务),是 AOP 的核心载体。
- 切点(Pointcut):定义 “哪些方法需要被增强”(比如指定包下的所有 Service 方法)。
- 通知(Advice):定义 “增强的时机和逻辑”,包括:
@Before:目标方法执行前执行@After:目标方法执行后执行(无论是否异常)@AfterReturning:目标方法正常返回后执行@AfterThrowing:目标方法抛出异常后执行@Around:环绕通知(最灵活,可控制目标方法的执行时机、参数、返回值)
- 连接点(JoinPoint):程序执行过程中可被增强的 “点”(比如方法调用、异常抛出),Spring 只支持方法级连接点。
二、Spring AOP 实现步骤(基于注解,最常用)
Spring AOP 有两种实现方式:注解式(推荐)和XML 配置式,这里重点讲注解式,步骤如下:
步骤 1:引入依赖(Maven/Gradle)
核心依赖是spring-context(已包含 AOP 基础),如果是 Spring Boot 项目,只需引入spring-boot-starter即可;非 Spring Boot 项目需手动引入:
xml
<!-- Spring核心+AOP依赖 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>6.1.4</version> <!-- 适配Java 17+,按需调整版本 --> </dependency> <!-- Spring AOP增强(可选,简化切点表达式) --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.1.4</version> </dependency>步骤 2:开启 AOP 注解支持
在 Spring 配置类上添加@EnableAspectJAutoProxy注解,启用 AOP 自动代理:
java
运行
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; // 配置类,开启AOP注解支持 @Configuration @ComponentScan("com.example") // 扫描指定包下的Bean @EnableAspectJAutoProxy public class SpringConfig { }步骤 3:定义目标类(被增强的业务类)
创建普通的 Spring Bean,作为 AOP 的 “目标对象”:
java
运行
import org.springframework.stereotype.Service; // 业务层Bean,作为AOP的目标类 @Service public class UserService { // 待增强的方法1 public void addUser(String username) { System.out.println("执行添加用户逻辑:" + username); // 可手动抛出异常测试@AfterThrowing:throw new RuntimeException("添加用户失败"); } // 待增强的方法2 public String getUserById(Integer id) { System.out.println("执行查询用户逻辑:id=" + id); return "用户-" + id; } }步骤 4:定义切面类(封装横切逻辑)
创建切面类,用@Aspect标记,同时标注为 Spring Bean(@Component),并定义切点和通知:
java
运行
import org.aspectj.lang.JoinPoint; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; // 1. 标记为切面类 + Spring Bean @Aspect @Component public class LogAspect { // 2. 定义切点(复用性高,可被多个通知引用) // 切点表达式:匹配com.example.service包下所有类的所有方法 @Pointcut("execution(* com.example.service.*.*(..))") public void servicePointcut() {} // 3. 定义通知(增强逻辑) // 前置通知:目标方法执行前执行 @Before("servicePointcut()") public void beforeAdvice(JoinPoint joinPoint) { // JoinPoint可获取目标方法名、参数等信息 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); System.out.println("[前置通知] 方法:" + methodName + ",参数:" + java.util.Arrays.toString(args)); } // 后置返回通知:目标方法正常返回后执行 @AfterReturning(value = "servicePointcut()", returning = "result") public void afterReturningAdvice(JoinPoint joinPoint, Object result) { String methodName = joinPoint.getSignature().getName(); System.out.println("[返回通知] 方法:" + methodName + ",返回值:" + result); } // 异常通知:目标方法抛出异常后执行 @AfterThrowing(value = "servicePointcut()", throwing = "e") public void afterThrowingAdvice(JoinPoint joinPoint, Exception e) { String methodName = joinPoint.getSignature().getName(); System.out.println("[异常通知] 方法:" + methodName + ",异常:" + e.getMessage()); } // 环绕通知(最灵活,可控制目标方法执行) @Around("servicePointcut()") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); System.out.println("[环绕前置] 开始执行方法:" + methodName); // 执行目标方法(必须调用,否则目标方法不会执行) Object result = joinPoint.proceed(); System.out.println("[环绕后置] 方法:" + methodName + "执行完成"); return result; // 可修改返回值 } }步骤 5:测试 AOP 效果
编写测试类,启动 Spring 容器并调用目标方法:
java
运行
import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class AopTest { public static void main(String[] args) { // 初始化Spring容器 AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(SpringConfig.class); // 获取目标Bean UserService userService = context.getBean(UserService.class); // 调用目标方法,触发AOP增强 userService.addUser("张三"); System.out.println("-----分割线-----"); userService.getUserById(1001); // 关闭容器 context.close(); } }执行结果(正常情况):
plaintext
[环绕前置] 开始执行方法:addUser [前置通知] 方法:addUser,参数:[张三] 执行添加用户逻辑:张三 [环绕后置] 方法:addUser执行完成 -----分割线----- [环绕前置] 开始执行方法:getUserById [前置通知] 方法:getUserById,参数:[1001] 执行查询用户逻辑:id=1001 [返回通知] 方法:getUserById,返回值:用户-1001 [环绕后置] 方法:getUserById执行完成三、关键知识点解析
切点表达式(execution 语法)上面用的
execution(* com.example.service.*.*(..))是最常用的切点表达式,拆解如下:*:返回值任意(第一个 *)com.example.service.*:匹配 service 包下的所有类.*:匹配类下的所有方法(..):方法参数任意(数量、类型都不限)其他常用表达式:- 匹配指定方法:
execution(public String com.example.service.UserService.getUserById(Integer)) - 匹配注解:
@annotation(org.springframework.transaction.annotation.Transactional)(匹配加了 @Transactional 的方法)
环绕通知的注意事项
ProceedingJoinPoint是JoinPoint的子类,只有环绕通知能使用。- 必须调用
joinPoint.proceed()才能执行目标方法,否则目标方法不会运行。 - 可通过
proceed(args)修改目标方法的参数,也可修改返回值。
AOP 的实现原理Spring AOP 基于动态代理实现:
- 如果目标类实现了接口:使用 JDK 动态代理(生成接口的代理类)。
- 如果目标类未实现接口:使用 CGLIB 动态代理(生成目标类的子类)。
@EnableAspectJAutoProxy(proxyTargetClass = true):强制使用 CGLIB 代理。
四、总结
- Spring AOP 实现的核心步骤:引入依赖 → 开启 AOP 注解 → 定义目标 Bean → 编写切面类(@Aspect + 切点 + 通知) → 测试。
- 注解式 AOP 是主流方式,核心注解包括
@EnableAspectJAutoProxy(开启 AOP)、@Aspect(标记切面)、@Pointcut(定义切点)、各类通知注解(@Before/@Around 等)。 - 环绕通知(@Around)最灵活,但使用成本稍高;简单场景优先用 @Before/@AfterReturning 等简单通知。