自定义注解配合AOP实现拦截的核心是“注解标记+切面识别+代理执行”,需确保注解保留策略为RUNTIME、切面注册为Bean、目标方法由Spring管理且调用经过代理。
自定义注解配合 AOP 实现拦截,核心在于“注解标记 + 切面识别 + 代理执行”。它不依赖硬编码调用,而是通过声明式方式将横切逻辑(如日志、权限、监控)织入目标方法。关键不在注解本身,而在于如何让 Spring 或 AspectJ 知道:哪些方法被这个注解标记了,以及在什么时机(@Before/@Around/@After)执行什么逻辑。
一、定义自定义注解(保留到运行时)
注解必须声明
,否则 AOP 切面无法在运行期通过反射获取;建议加上
明确作用位置:
这个注解可加在方法上(如
),也可用于类(表示该类所有 public 方法都生效),后续切面会据此匹配。
二、编写切面(Aspect),用 @Pointcut 匹配带注解的方法
Spring AOP 中,切面类需加
和
(或手动注册为 Bean),然后用
定义拦截规则:
}
注意:
-
是最常用且稳定的匹配方式,支持注解参数读取;
- 若想同时支持类级注解(即整个类的方法都生效),可组合使用:
;
-
匹配类上有该注解,
匹配方法上有该注解。
三、确保代理机制生效(Spring AOP 默认基于 JDK 动态代理或 CGLIB)
Spring AOP 默认对接口使用 JDK 代理,对无接口的类自动启用 CGLIB(需添加
,它已含
和
):
目标类必须是 Spring 容器管理的 Bean(即由
/
等声明并被扫描到);
切面类(
)也必须是 Spring Bean(加
或显式
注册);
不要在同一个类中调用本类的被拦截方法(如
),这会绕过代理 —— 应改为注入自身或使用
(需开启
);
若需更强大能力(如构造器拦截、private 方法拦截),则需切换至 AspectJ 编译时织入(LTW/CTW),此时不再依赖 Spring 代理,而是通过 ajc 编译器直接修改字节码。
四、进阶:用 AspectJ 原生支持(脱离 Spring 代理限制)
若项目允许引入 AspectJ,可完全摆脱 Spring 代理约束:
添加 Maven 依赖:
和
;
启用编译时织入(CTW):配置
,或运行时织入(LTW):添加
启动参数;
切面写法类似,但可写更灵活的 pointcut,例如:
直接匹配带注解的方法签名;
甚至支持
拦截调用点(非执行点);
此时无需 Spring 容器,纯 Java 类加注解即可被拦截,适合非 Spring 环境或对性能/覆盖范围要求更高的场景。
不复杂但容易忽略:注解是否 RUNTIME、切面是否注册为 Bean、目标方法是否被 Spring 管理、调用是否经过代理 —— 这四点齐备,自定义注解 AOP 就能稳定工作。
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.METHOD, ElementType.TYPE})@Target({METHOD, TYPE})
@Retention(RUNTIME)
@Documented
public @interface LogExecutionTime {
String value() default "";
boolean showArgs() default false;
}@LogExecutionTime(showArgs = true)@Aspect@Component@Pointcut@Aspect
@Component
public class LogExecutionAspect {
// 匹配任意被 @LogExecutionTime 标记的 public 方法
@Pointcut("@annotation(com.example.LogExecutionTime)")
public void logExecutionPointcut() {}
@Around("logExecutionPointcut()")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
try {
Object result = joinPoint.proceed(); // 执行原方法
long duration = System.currentTimeMillis() - start;
LogExecutionTime ann = joinPoint.getSignature()
.getDeclaringType()
.getMethod(joinPoint.getSignature().getName(),
getParamTypes(joinPoint))
.getAnnotation(LogExecutionTime.class);
if (ann != null && ann.showArgs()) {
System.out.println("Args: " + Arrays.toString(joinPoint.getArgs()));
}
System.out.printf("Method %s took %d ms%n",
joinPoint.getSignature(), duration);
return result;
} catch (Throwable e) {
long duration = System.currentTimeMillis() - start;
System.err.printf("Method %s failed after %d ms: %s%n",
joinPoint.getSignature(), duration, e.getMessage());
throw e;
}
}
private Class>[] getParamTypes(ProceedingJoinPoint jp) {
return Arrays.stream(jp.getArgs())
.map(Object::getClass)
.toArray(Class[]::new);
}@annotation(com.example.LogExecutionTime)@Pointcut("@annotation(com.example.LogExecutionTime) || @within(com.example.LogExecutionTime)")@within@annotationspring-boot-starter-aopspring-aopaspectjweaver@Service@Component@Aspect@Component@Beanthis.doSomething()AopContext.currentProxy()expose-proxy=trueorg.aspectj:aspectjrtorg.aspectj:aspectjweaveraspectj-maven-plugin-javaagent:aspectjweaver.jarexecution(@com.example.LogExecutionTime * *(..))call(@com.example.LogExecutionTime * *(..))