Spring前置通知(Before Advice)
外观
Spring前置通知(Before Advice)[编辑 | 编辑源代码]
概述[编辑 | 编辑源代码]
Spring前置通知是Spring AOP(面向切面编程)中五种通知类型之一,它会在目标方法执行前被自动触发。前置通知通常用于执行权限校验、日志记录、参数预处理等横切关注点(Cross-Cutting Concerns),其核心特点是不中断原始流程,但可以通过抛出异常阻止目标方法执行。
前置通知通过`@Before`注解或XML配置实现,是AOP中最简单直观的通知类型。其执行时机如下图所示:
核心特性[编辑 | 编辑源代码]
- 执行时机:目标方法执行前(JoinPoint之前)
- 返回值:void(不能修改返回值)
- 中断能力:可通过抛出异常阻止目标方法执行
- 典型应用:
* 参数验证 * 访问控制 * 日志记录 * 上下文初始化
代码实现[编辑 | 编辑源代码]
基于注解的方式[编辑 | 编辑源代码]
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class LoggingAspect {
// 匹配com.example.service包下所有类的所有方法
@Before("execution(* com.example.service.*.*(..))")
public void logBeforeExecution() {
System.out.println("[前置通知] 准备执行方法");
}
// 带参数访问的示例
@Before("execution(* com.example.service.UserService.createUser(..)) && args(username,..)")
public void validateUsername(String username) {
if(username == null || username.trim().isEmpty()) {
throw new IllegalArgumentException("用户名不能为空");
}
System.out.println("[验证] 用户名: " + username);
}
}
基于XML的配置[编辑 | 编辑源代码]
<aop:config>
<aop:aspect id="securityAspect" ref="securityAdvice">
<aop:before
pointcut="execution(* com.example.service.PaymentService.*(..))"
method="checkAuthorization"/>
</aop:aspect>
</aop:config>
<bean id="securityAdvice" class="com.example.aspect.SecurityAspect"/>
对应切面类:
public class SecurityAspect {
public void checkAuthorization() {
// 模拟权限检查
if(!UserContext.getCurrentUser().hasRole("ADMIN")) {
throw new SecurityException("权限不足");
}
}
}
技术细节[编辑 | 编辑源代码]
JoinPoint访问[编辑 | 编辑源代码]
前置通知可以通过`JoinPoint`参数获取目标方法信息:
@Before("execution(* com.example..*.*(..))")
public void beforeAdvice(JoinPoint jp) {
System.out.println("方法签名: " + jp.getSignature());
System.out.println("参数: " + Arrays.toString(jp.getArgs()));
System.out.println("目标对象: " + jp.getTarget().getClass());
}
执行顺序[编辑 | 编辑源代码]
当多个前置通知匹配同一个连接点时,执行顺序由以下规则决定:
- 同一切面中的通知按声明顺序执行
- 不同切面可通过`@Order`注解或实现`Ordered`接口控制顺序
数学表示(执行顺序公式):
实际应用案例[编辑 | 编辑源代码]
案例1:API计时器[编辑 | 编辑源代码]
@Aspect
@Component
public class PerformanceMonitorAspect {
private ThreadLocal<Long> startTime = new ThreadLocal<>();
@Before("execution(* com.example.api..*.*(..))")
public void recordStartTime() {
startTime.set(System.currentTimeMillis());
}
// 需要配合后置通知完成计时功能
}
案例2:数据库操作审计[编辑 | 编辑源代码]
@Aspect
public class AuditAspect {
@Autowired
private AuditLogRepository logRepository;
@Before("execution(* org.springframework.data.repository.CrudRepository.save*(..))")
public void logBeforeSave(JoinPoint jp) {
Object entity = jp.getArgs()[0];
AuditLog log = new AuditLog(
"PRE_SAVE",
entity.getClass().getSimpleName(),
UserContext.getCurrentUser()
);
logRepository.save(log);
}
}
常见问题[编辑 | 编辑源代码]
Q1:前置通知能修改方法参数吗?[编辑 | 编辑源代码]
可以,但需要通过特殊方式实现:
@Aspect
public class ParamModificationAspect {
@Before("execution(* com.example..*(..)) && args(param,..)")
public void modifyParam(JoinPoint jp, Object param) {
Object[] args = jp.getArgs();
args[0] = "修改后的值"; // 直接修改参数数组
}
}
注意:这种方式在Spring 5.2+版本中需要配置`@EnableAspectJAutoProxy(exposeProxy = true)`
Q2:如何避免循环通知调用?[编辑 | 编辑源代码]
在切点表达式中使用`within()`排除切面类自身:
@Before("execution(* com.example..*(..)) && !within(com.example.aspect..*)")
public void safeAdvice() {
// 不会拦截切面类中的方法
}
最佳实践[编辑 | 编辑源代码]
- 保持前置通知的轻量级,避免复杂业务逻辑
- 对关键校验逻辑使用显式异常(如`IllegalArgumentException`)
- 为通知添加清晰的日志记录
- 使用细粒度的切点表达式提高匹配精度
- 考虑使用组合切点(`@Pointcut`)提高重用性
总结[编辑 | 编辑源代码]
Spring前置通知作为AOP的基础组件,提供了在方法边界插入横切逻辑的能力。通过合理使用前置通知,可以实现:
- 集中式的校验逻辑
- 非侵入式的监控
- 一致的预处理流程
- 清晰的关注点分离
其设计体现了AOP的核心思想:将横切关注点与核心业务逻辑解耦,是Spring框架中实现DRY(Don't Repeat Yourself)原则的重要工具。