跳转到内容

Spring前置通知(Before Advice)

来自代码酷

Spring前置通知(Before Advice)[编辑 | 编辑源代码]

概述[编辑 | 编辑源代码]

Spring前置通知是Spring AOP(面向切面编程)中五种通知类型之一,它会在目标方法执行前被自动触发。前置通知通常用于执行权限校验、日志记录、参数预处理等横切关注点(Cross-Cutting Concerns),其核心特点是不中断原始流程,但可以通过抛出异常阻止目标方法执行。

前置通知通过`@Before`注解或XML配置实现,是AOP中最简单直观的通知类型。其执行时机如下图所示:

sequenceDiagram participant Client participant Proxy participant Target participant Advice Client->>Proxy: 调用方法() Proxy->>Advice: 执行前置通知 alt 通知正常完成 Advice->>Target: 调用目标方法 Target-->>Proxy: 返回结果 else 通知抛出异常 Advice--x Proxy: 终止流程 end Proxy-->>Client: 返回结果/异常

核心特性[编辑 | 编辑源代码]

  • 执行时机:目标方法执行前(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());
}

执行顺序[编辑 | 编辑源代码]

当多个前置通知匹配同一个连接点时,执行顺序由以下规则决定:

  1. 同一切面中的通知按声明顺序执行
  2. 不同切面可通过`@Order`注解或实现`Ordered`接口控制顺序

数学表示(执行顺序公式): ExecutionOrder={DeclarationOrder同一切面内OrderAnnotationValue跨切面情况

实际应用案例[编辑 | 编辑源代码]

案例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)原则的重要工具。