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