跳转到内容
主菜单
主菜单
移至侧栏
隐藏
导航
首页
最近更改
随机页面
MediaWiki帮助
代码酷
搜索
搜索
中文(中国大陆)
外观
创建账号
登录
个人工具
创建账号
登录
未登录编辑者的页面
了解详情
贡献
讨论
编辑“︁
Spring前置通知(Before Advice)
”︁
页面
讨论
大陆简体
阅读
编辑
编辑源代码
查看历史
工具
工具
移至侧栏
隐藏
操作
阅读
编辑
编辑源代码
查看历史
常规
链入页面
相关更改
特殊页面
页面信息
外观
移至侧栏
隐藏
您的更改会在有权核准的用户核准后向读者展示。
警告:
您没有登录。如果您进行任何编辑,您的IP地址会公开展示。如果您
登录
或
创建账号
,您的编辑会以您的用户名署名,此外还有其他益处。
反垃圾检查。
不要
加入这个!
= Spring前置通知(Before Advice) = == 概述 == '''Spring前置通知'''是Spring AOP(面向切面编程)中五种通知类型之一,它会在目标方法'''执行前'''被自动触发。前置通知通常用于执行权限校验、日志记录、参数预处理等横切关注点(Cross-Cutting Concerns),其核心特点是'''不中断原始流程''',但可以通过抛出异常阻止目标方法执行。 前置通知通过`@Before`注解或XML配置实现,是AOP中'''最简单直观'''的通知类型。其执行时机如下图所示: <mermaid> 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: 返回结果/异常 </mermaid> == 核心特性 == * '''执行时机''':目标方法执行前(JoinPoint之前) * '''返回值''':void(不能修改返回值) * '''中断能力''':可通过抛出异常阻止目标方法执行 * '''典型应用''': * 参数验证 * 访问控制 * 日志记录 * 上下文初始化 == 代码实现 == === 基于注解的方式 === <syntaxhighlight lang="java"> 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); } } </syntaxhighlight> === 基于XML的配置 === <syntaxhighlight lang="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"/> </syntaxhighlight> 对应切面类: <syntaxhighlight lang="java"> public class SecurityAspect { public void checkAuthorization() { // 模拟权限检查 if(!UserContext.getCurrentUser().hasRole("ADMIN")) { throw new SecurityException("权限不足"); } } } </syntaxhighlight> == 技术细节 == === JoinPoint访问 === 前置通知可以通过`JoinPoint`参数获取目标方法信息: <syntaxhighlight lang="java"> @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()); } </syntaxhighlight> === 执行顺序 === 当多个前置通知匹配同一个连接点时,执行顺序由以下规则决定: # 同一切面中的通知按声明顺序执行 # 不同切面可通过`@Order`注解或实现`Ordered`接口控制顺序 数学表示(执行顺序公式): <math> ExecutionOrder = \begin{cases} DeclarationOrder & \text{同一切面内} \\ OrderAnnotationValue & \text{跨切面情况} \end{cases} </math> == 实际应用案例 == === 案例1:API计时器 === <syntaxhighlight lang="java"> @Aspect @Component public class PerformanceMonitorAspect { private ThreadLocal<Long> startTime = new ThreadLocal<>(); @Before("execution(* com.example.api..*.*(..))") public void recordStartTime() { startTime.set(System.currentTimeMillis()); } // 需要配合后置通知完成计时功能 } </syntaxhighlight> === 案例2:数据库操作审计 === <syntaxhighlight lang="java"> @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); } } </syntaxhighlight> == 常见问题 == === Q1:前置通知能修改方法参数吗? === 可以,但需要通过特殊方式实现: <syntaxhighlight lang="java"> @Aspect public class ParamModificationAspect { @Before("execution(* com.example..*(..)) && args(param,..)") public void modifyParam(JoinPoint jp, Object param) { Object[] args = jp.getArgs(); args[0] = "修改后的值"; // 直接修改参数数组 } } </syntaxhighlight> 注意:这种方式在Spring 5.2+版本中需要配置`@EnableAspectJAutoProxy(exposeProxy = true)` === Q2:如何避免循环通知调用? === 在切点表达式中使用`within()`排除切面类自身: <syntaxhighlight lang="java"> @Before("execution(* com.example..*(..)) && !within(com.example.aspect..*)") public void safeAdvice() { // 不会拦截切面类中的方法 } </syntaxhighlight> == 最佳实践 == * 保持前置通知的'''轻量级''',避免复杂业务逻辑 * 对关键校验逻辑使用显式异常(如`IllegalArgumentException`) * 为通知添加清晰的日志记录 * 使用细粒度的切点表达式提高匹配精度 * 考虑使用组合切点(`@Pointcut`)提高重用性 == 总结 == Spring前置通知作为AOP的基础组件,提供了在方法边界插入横切逻辑的能力。通过合理使用前置通知,可以实现: * 集中式的校验逻辑 * 非侵入式的监控 * 一致的预处理流程 * 清晰的关注点分离 其设计体现了AOP的核心思想:'''将横切关注点与核心业务逻辑解耦''',是Spring框架中实现DRY(Don't Repeat Yourself)原则的重要工具。 [[Category:后端框架]] [[Category:Spring]] [[Category:Spring AOP]]
摘要:
请注意,所有对代码酷的贡献均被视为依照知识共享署名-非商业性使用-相同方式共享发表(详情请见
代码酷:著作权
)。如果您不希望您的文字作品被随意编辑和分发传播,请不要在此提交。
您同时也向我们承诺,您提交的内容为您自己所创作,或是复制自公共领域或类似自由来源。
未经许可,请勿提交受著作权保护的作品!
取消
编辑帮助
(在新窗口中打开)