跳转到主内容
趣航编程网 - 趣学编程,启航技术之路!

如何利用 Spring AOP 的 AspectJ 切面配合自定义注解实现业务方法的动态权限监控

@Around是唯一能阻断方法执行的AOP通知,通过ProceedingJoinPoint.proceed()控制是否调用目标方法;@Before和@After无法中断流程,权限校验必须在proceed前抛异常。
@Around
拦截必须用环绕通知,不是
@Before
或
@After
Spring AOP 中真正能做权限放行/阻断的只有
@Around
。它拿到的是
ProceedingJoinPoint
,可以决定是否调用
joinPoint.proceed()
;而
@Before
只能执行前置逻辑,无法阻止方法执行;
@After
更是在方法已结束之后才触发,此时抛异常也晚了。 权限校验失败时,必须在
joinPoint.proceed()
之前 throw 异常(如
AccessDeniedException
),否则业务方法照常执行 不要试图在
@Before
里 throw 异常来“中断”流程——Spring AOP 不会因此跳过原方法 若需记录执行耗时或返回结果,
@Around
也能统一包裹,比组合多个通知更简洁
PermissionCheck
注解必须带
@Retention(RetentionPolicy.RUNTIME)
自定义注解若在运行期被
@Around("@annotation(permissionCheck)")
匹配,就必须保留到运行时。常见错误是只加了
@Retention(RetentionPolicy.CLASS)
或漏写
@Retention
(默认为
SOURCE
),导致切面根本找不到该注解。 缺少
@Retention(RetentionPolicy.RUNTIME)
→ 切面匹配失败,方法被静默放过
@Target
至少要包含
ElementType.METHOD
,否则无法标注在方法上 如果还想支持类级别统一控制,额外加上
ElementType.TYPE
authService.hasPermission()
调用前必须确保上下文可用 权限判断依赖当前用户身份,而 Spring Security 的
SecurityContextHolder.getContext().getAuthentication()
或自研的
AuthService.getCurrentUserId(request)
都要求请求上下文存在。常见失效场景包括: 在异步线程(如
@Async
方法)中调用被切面的方法 →
RequestContextHolder
为空,
getCurrentUserId()
抛
IllegalStateException
单元测试未模拟
ServletRequestAttributes
→
RequestContextHolder.currentRequestAttributes()
返回 null Filter 链顺序错误,认证信息未提前注入(如自定义 JWT Filter 放在 Spring Security Filter 之后) 建议在切面开头加空值防护:
ServletRequestAttributes attrs = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); if (attrs == null || attrs.getRequest() == null) { throw new IllegalStateException("No HTTP request context available"); }
@Aspect
类必须被 Spring 容器管理且启用 AOP 即使写了
@Aspect
和
@Component
,若没开启 AOP 支持,切面不会生效。Spring Boot 2.6+ 默认启用,但老项目或手动配置时容易遗漏: 确保引入了
spring-boot-starter-aop
(含
aspectjweaver
) 检查是否误加了
@EnableAspectJAutoProxy(proxyTargetClass = false)
却让目标类没实现接口 → 导致 JDK 代理失败,CGLIB 未启用 若目标方法是
private
或
final
,AOP 无法代理(CGLIB 也不支持 final 方法) 切面类不能是
new
出来的对象,必须由 Spring 容器实例化(否则
@Autowired
的
authService
为 null) 最常被忽略的一点:权限判断逻辑本身不该依赖被代理方法的参数,除非你显式从
joinPoint.getArgs()
提取并传入
hasPermission()
—— 否则像“删除 ID 为 X 的用户”这类细粒度权限,光靠注解字符串
"user:delete"
是不够的,得结合运行时参数做二次校验。

相关文章