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

基于注解实现AOP拦截器逻辑_自定义注解如何配合AspectJ或原生代理实现拦截

自定义注解配合AOP实现拦截的核心是“注解标记+切面识别+代理执行”,需确保注解保留策略为RUNTIME、切面注册为Bean、目标方法由Spring管理且调用经过代理。 自定义注解配合 AOP 实现拦截,核心在于“注解标记 + 切面识别 + 代理执行”。它不依赖硬编码调用,而是通过声明式方式将横切逻辑(如日志、权限、监控)织入目标方法。关键不在注解本身,而在于如何让 Spring 或 AspectJ 知道:哪些方法被这个注解标记了,以及在什么时机(@Before/@Around/@After)执行什么逻辑。 一、定义自定义注解(保留到运行时) 注解必须声明
@Retention(RetentionPolicy.RUNTIME)
,否则 AOP 切面无法在运行期通过反射获取;建议加上
@Target({ElementType.METHOD, ElementType.TYPE})
明确作用位置:
@Target({METHOD, TYPE}) @Retention(RUNTIME) @Documented public @interface LogExecutionTime { String value() default ""; boolean showArgs() default false; }
这个注解可加在方法上(如
@LogExecutionTime(showArgs = true)
),也可用于类(表示该类所有 public 方法都生效),后续切面会据此匹配。 二、编写切面(Aspect),用 @Pointcut 匹配带注解的方法 Spring AOP 中,切面类需加
@Aspect
和
@Component
(或手动注册为 Bean),然后用
@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
匹配类上有该注解,
@annotation
匹配方法上有该注解。 三、确保代理机制生效(Spring AOP 默认基于 JDK 动态代理或 CGLIB) Spring AOP 默认对接口使用 JDK 代理,对无接口的类自动启用 CGLIB(需添加
spring-boot-starter-aop
,它已含
spring-aop
和
aspectjweaver
): 目标类必须是 Spring 容器管理的 Bean(即由
@Service
/
@Component
等声明并被扫描到); 切面类(
@Aspect
)也必须是 Spring Bean(加
@Component
或显式
@Bean
注册); 不要在同一个类中调用本类的被拦截方法(如
this.doSomething()
),这会绕过代理 —— 应改为注入自身或使用
AopContext.currentProxy()
(需开启
expose-proxy=true
); 若需更强大能力(如构造器拦截、private 方法拦截),则需切换至 AspectJ 编译时织入(LTW/CTW),此时不再依赖 Spring 代理,而是通过 ajc 编译器直接修改字节码。 四、进阶:用 AspectJ 原生支持(脱离 Spring 代理限制) 若项目允许引入 AspectJ,可完全摆脱 Spring 代理约束: 添加 Maven 依赖:
org.aspectj:aspectjrt
和
org.aspectj:aspectjweaver
; 启用编译时织入(CTW):配置
aspectj-maven-plugin
,或运行时织入(LTW):添加
-javaagent:aspectjweaver.jar
启动参数; 切面写法类似,但可写更灵活的 pointcut,例如:
execution(@com.example.LogExecutionTime * *(..))
直接匹配带注解的方法签名; 甚至支持
call(@com.example.LogExecutionTime * *(..))
拦截调用点(非执行点); 此时无需 Spring 容器,纯 Java 类加注解即可被拦截,适合非 Spring 环境或对性能/覆盖范围要求更高的场景。 不复杂但容易忽略:注解是否 RUNTIME、切面是否注册为 Bean、目标方法是否被 Spring 管理、调用是否经过代理 —— 这四点齐备,自定义注解 AOP 就能稳定工作。

相关文章