Spring AOP @Before (前置通知): 在目标方法执行前做什么?
核心定义
@Before
是 Spring AOP 中的一种**通知(Advice)**类型。它的核心作用是:
在“连接点(Join Point)”,也就是目标方法被执行之前,执行一段特定的逻辑代码。
你可以把它想象成一个“门卫”。在有人(调用方)要进入一个重要的房间(目标方法)之前,门卫(@Before
通知)会先执行一系列检查或准备工作,比如核对身份、记录来访时间等。
@Before
通知能做什么?(主要应用场景)
前置通知非常适合执行一些“准备”或“预检”性质的任务,这些任务不影响目标方法的执行结果,但对整个流程至关重要。
-
日志记录 (Logging/Auditing)
- 这是最经典的应用。在方法执行前,记录下谁、在什么时间、尝试调用哪个方法,并传入了什么参数。
- 示例:“用户
admin
正在尝试执行updateProduct
操作,参数为:Product{id=101, name='New Laptop'}
”。
-
权限校验 (Authorization/Security Check)
- 在执行敏感操作(如删除数据、修改金额)前,检查当前登录的用户是否拥有足够的权限。
- 如果没有权限,可以直接在
@Before
通知里抛出一个异常(如PermissionDeniedException
),这样目标方法就根本不会被执行,从而保证了系统的安全。
-
参数校验 (Parameter Validation)
- 虽然 JSR-303/JSR-380 (Bean Validation) 是更通用的方式,但对于一些复杂的、跨领域的业务校验,使用 AOP 也很方便。
- 例如,检查传入的参数是否为
null
,数值是否在有效范围内等。如果校验失败,同样可以抛出异常来中断操作。
-
数据准备 (Data Preparation)
- 在方法执行前,可能需要准备一些上下文环境信息。
- 例如,将某些信息设置到
ThreadLocal
中,以便目标方法或后续的逻辑可以方便地获取。
-
开启事务或设置事务属性(不推荐,但技术上可行)
- 虽然 Spring 提供了更强大的
@Transactional
注解来管理事务,但理论上你也可以在@Before
中手动开启一个事务。不过,这种方式不推荐。
- 虽然 Spring 提供了更强大的
@Before
的一个关键特点:无法阻止目标方法执行(除非抛异常)
@Before
通知本身没有“否决权”。一旦 @Before
的代码成功执行完毕(没有抛出任何异常),控制权会自动交还给 AOP 框架,然后目标方法会被继续执行。
- 唯一能阻止目标方法执行的方式:在
@Before
通知的代码中主动抛出一个异常。异常会中断当前的执行流程,使得目标方法没有机会被调用。
如果你需要更复杂的控制,比如:
- 根据条件决定是否执行目标方法。
- 修改传入目标方法的参数。
- 修改目标方法的返回值。
那么你应该使用功能更强大的 @Around
(环绕通知)。
代码示例
让我们看一个具体的例子,实现一个简单的日志记录功能。
1. 添加 AOP 依赖 (Maven)
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 创建一个业务服务类 (目标对象)
package com.example.service;import org.springframework.stereotype.Service;@Service
public class UserService {public String findUserById(Long id) {System.out.println("--- 核心业务逻辑:正在根据 ID 查询用户... ---");if (id < 1) {throw new IllegalArgumentException("用户ID必须大于0");}return "User" + id;}
}
3. 创建一个切面 (Aspect) 并定义 @Before
通知
package com.example.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import java.util.Arrays;@Aspect // 声明这是一个切面类
@Component // 将其注册为 Spring Bean
public class LoggingAspect {// 1. 定义一个可重用的切点 (Pointcut)// 拦截 com.example.service 包下所有类的所有公共方法@Pointcut("execution(public * com.example.service.*.*(..))")public void serviceLayerPointcut() {}// 2. 定义前置通知,并引用上面的切点@Before("serviceLayerPointcut()")public void logBefore(JoinPoint joinPoint) { // JoinPoint 对象包含了目标方法的信息// 获取方法签名String methodName = joinPoint.getSignature().getName();// 获取方法参数Object[] args = joinPoint.getArgs();System.out.println("==================================================");System.out.printf("[AOP 前置通知]: 方法 [%s] 即将执行...%n", methodName);System.out.printf("[AOP 前置通知]: 参数为: %s%n", Arrays.toString(args));System.out.println("==================================================");}
}
4. 运行代码
当你调用 userService.findUserById(101L)
时,控制台的输出会是:
==================================================
[AOP 前置通知]: 方法 [findUserById] 即将执行...
[AOP 前置通知]: 参数为: [101]
==================================================
--- 核心业务逻辑:正在根据 ID 查询用户... ---
可以看到,logBefore
方法(前置通知)在 findUserById
方法(目标方法)之前被成功执行了。
总结
特性 | 描述 |
---|---|
执行时机 | 在目标方法被调用之前执行。 |
核心用途 | 日志记录、权限校验、参数校验、数据准备等预处理工作。 |
控制能力 | 不能直接阻止目标方法执行,也不能修改参数或返回值。 |
如何中断流程 | 只能通过抛出异常来间接阻止目标方法的执行。 |
关键参数 | 可以注入 JoinPoint 对象,用以获取目标方法的签名、参数等元数据。 |
简而言之,@Before
是一个强大而简单的工具,用于在核心业务逻辑开始前“插入”一段通用的横切逻辑。