导读:本期聚焦于湖南程序员创作的《自定义注解如何配合AspectJ或原生代理实现拦截逻辑?》,敬请观看详情。自定义注解配合AOP拦截是Java后台削减重复代码的重要手段。不少人以为加了注解就会自动生效,其实底层依赖AspectJ切点表达式或Spring原生代理机制。本文先厘清注解本身只是标记、不携带行为的误区,再说明如何通过@Around等通知读取注解属性完成权限校验、日志埋点等拦截。对比编译期织入与运行期代理的差异后,给出可复用的代码模板,帮助开发者少走弯路,让注解真正驱动业务逻辑。

在Java企业级开发中,自定义注解常被用来标记需要特殊处理的方法,例如接口鉴权、操作日志、防重复提交等。但注解仅仅是元数据,本身不具备任何执行能力。要让注解发挥作用,必须借助AOP将拦截逻辑织入到标记了注解的方法调用前后。实现方式主要分为两类:一是利用AspectJ完整的切面框架,通过切点表达式匹配注解;二是基于JDK动态代理或CGLIB的原生代理机制,在代理对象中反射读取注解并执行增强。理解这两种路径的底层差异,是写出稳定拦截器的前提。

自定义注解如何配合AspectJ或原生代理实现拦截逻辑?

自定义注解的定义与元注解配置

实现注解拦截的第一步是定义自己的注解。Java中用@interface声明注解类型,并通过@Target@Retention等元注解控制它的作用范围和生命周期。如果希望注解在运行期被AOP读取,@Retention必须设置为RetentionPolicy.RUNTIME;若使用编译期AspectJ织入,也可设为CLASS。作用目标通常选ElementType.METHOD,表示贴在方法上。注解内部可以定义属性,例如超时时间、日志描述,这些属性会在切面中被读取并影响拦截行为。

下面给出一个典型的自定义注解示例,用于标记需要记录耗时的方法。注解只有一个value属性,用来说明业务名称。注意,如果属性名为value且只设置一个值,使用注解时可省略属性名。这种轻量设计能让业务代码保持整洁,同时给切面提供足够上下文。

import java.lang.annotation.*;

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTime {
    String value() default "";
}

定义完注解后,业务方法只需添加@LogTime("下单接口")即可完成标记。此时注解如同贴在方法上的标签,但还没有任何代码去处理它。很多初学者误以为框架会自动识别并输出日志,其实从JVM角度看,注解在运行期只是可通过反射获取的Annotation对象,真正触发动作的是后续编写的切面或代理逻辑。

基于AspectJ注解切面完成拦截

Spring AOP整合了AspectJ的注解风格,开发者可以用@Aspect@Around等注解快速编写拦截器。切点的核心是表达式,例如@annotation(com.demo.LogTime)表示匹配所有带LogTime注解的方法。在@Around通知中,通过ProceedingJoinPoint获取方法签名,再读取注解实例,就能在方法执行前后插入逻辑。这种方式的优势是解耦彻底,业务方法零侵入,且切点表达式支持精细过滤。

下面的代码展示了一个完整的AspectJ切面。它在目标方法执行前打印开始时间,执行后计算耗时并输出。通过method.getAnnotation(LogTime.class)拿到注解后,使用其value属性作为日志标识。若方法抛异常,切面也会记录失败信息。这种结构可直接复用于监控、限流等场景。

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import java.lang.reflect.Method;

@Aspect
@Component
public class LogTimeAspect {

    @Around("@annotation(com.demo.LogTime)")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        LogTime logTime = method.getAnnotation(LogTime.class);
        String name = logTime != null ? logTime.value() : "未知";
        long start = System.currentTimeMillis();
        try {
            return pjp.proceed();
        } finally {
            long cost = System.currentTimeMillis() - start;
            System.out.println(name + " 耗时: " + cost + "ms");
        }
    }
}

使用AspectJ风格时,需注意Spring容器必须开启@EnableAspectJAutoProxy,且切面类要被扫描为Bean。如果项目使用编译期织入,还需配置AspectJ编译器插件,此时切点不仅限于Spring管理的Bean,连普通POJO也能拦截。但这种重织入会增加构建复杂度,一般业务系统用运行期代理已足够。对比来看,AspectJ表达式灵活、功能全,适合统一治理;原生代理则更轻,下面具体说明。

原生JDK与CGLIB代理读取注解拦截

若不引入AspectJ,也可利用Spring的原生代理机制。当目标类实现接口时用JDK动态代理,否则用CGLIB子类代理。代理对象在调用真实方法前,会经过MethodInterceptorInvocationHandler,这里同样可以反射读取方法上的注解。与原生代理配合自定义注解的思路是:在拦截器中判断method.isAnnotationPresent(LogTime.class),若存在则执行增强。这种方式不依赖切点表达式,逻辑直观,但需自己管理代理创建。

以下示例用CGLIB的MethodInterceptor实现同样耗时统计。它手动创建代理对象,在intercept方法中拿到目标方法和注解,控制执行与统计。可以看出,注解在这里只是判断条件,真正的拦截完全由代理链完成。对于不想引入AOP框架的老项目,这种写法侵入小、依赖少。

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

public class LogTimeProxy implements MethodInterceptor {

    public Object getInstance(Class<?> targetClass) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(targetClass);
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args,
                            MethodProxy proxy) throws Throwable {
        if (method.isAnnotationPresent(LogTime.class)) {
            LogTime lt = method.getAnnotation(LogTime.class);
            long start = System.currentTimeMillis();
            try {
                return proxy.invokeSuper(obj, args);
            } finally {
                System.out.println(lt.value() + " 耗时: "
                        + (System.currentTimeMillis() - start) + "ms");
            }
        }
        return proxy.invokeSuper(obj, args);
    }
}

原生代理的局限在于只能拦截通过代理对象发起的调用,自调用(同类方法内部调用)会绕过代理,导致注解失效。AspectJ编译期织入则无此问题。此外,JDK动态代理要求接口存在,CGLIB不能代理final类。因此在选型时,若系统已用Spring且需全局规范,优先AspectJ;若仅需局部增强且追求简单,原生代理配合注解也完全可行。掌握这两条路线,自定义注解就能真正驱动拦截逻辑落地。

AOP自定义注解AspectJ修改时间:2026-08-18 09:44:30

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。