自定义注解实战:用 AOP 让代码“会说话”

简介: 本文详解如何在Spring中通过自定义注解+AOP实现方法日志记录,剖析注解原理与AOP拦截机制,并拓展至权限、缓存、校验等实用场景,提升代码可维护性与优雅度。

在 Spring 项目中,我们经常看到 @Transactional@Cacheable@PreAuthorize 等注解——它们简洁优雅,却能自动完成事务、缓存、权限校验等复杂逻辑。

这些“魔法”的背后,正是 自定义注解 + AOP(面向切面编程) 的组合。

本文将手把手带你实现一个用于方法日志记录的自定义注解,并深入理解其原理与扩展可能。


一、什么是自定义注解?

Java 注解(Annotation)本质上是一种元数据,它不直接参与程序逻辑,但可以被编译器、运行时或框架读取并执行相应行为。

要让注解“活”起来,关键在于:

  • 正确使用元注解(如 @Target@Retention);
  • 结合 AOP 或反射机制 在运行时拦截并处理。

二、实战:实现一个日志注解

1. 定义基础实体与服务(略)

假设已有:

  • User 实体类
  • UserDAO 数据访问层
  • UserService 业务层
  • UserController 控制器
@RestController
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/user/{id}")
    public User findUser(@PathVariable Integer id) {
        return userService.findUserById(id);
    }
}

现在,我们希望在调用 findUser 时自动打印日志,但不想写重复的 log.info(...)


2. 创建自定义注解

import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可通过反射获取
@Target(ElementType.METHOD)          // 仅用于方法
public @interface CustomAnnotation {
    String name() default "";        // 注解属性1
    String value() default "";       // 注解属性2(习惯命名为 value,可简写)
}

关键元注解说明:

元注解 作用
@Documented 生成 Javadoc 时包含该注解
@Retention(RUNTIME) 必须为 RUNTIME,否则 AOP 无法通过反射读取
@Target(METHOD) 限定只能用在方法上

💡 注解中的方法 = 属性。使用时:@CustomAnnotation(name = "xxx", value = "yyy")

若只有 value 属性,可简写为:@CustomAnnotation("yyy")


3. 使用注解标记方法

@CustomAnnotation(name = "findUser", value = "根据ID查找用户")
@GetMapping("/user/{id}")
public User findUser(@PathVariable Integer id) {
    return userService.findUserById(id);
}

此时注解只是“贴标签”,还没任何行为。


4. 用 AOP 拦截注解并执行逻辑

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LogAspect {
    // 定义切入点:所有被 @CustomAnnotation 标记的方法
    @Pointcut("@annotation(cn.example.demo.CustomAnnotation)")
    public void annotatedMethod() {}
    // 在方法执行前拦截
    @Before("annotatedMethod() && @annotation(annotation)")
    public void logBefore(JoinPoint joinPoint, CustomAnnotation annotation) {
        String className = joinPoint.getSignature().getDeclaringTypeName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("=== 日志拦截 ===");
        System.out.println("类名: " + className);
        System.out.println("方法: " + methodName);
        System.out.println("功能: " + annotation.value());
        System.out.println("标识: " + annotation.name());
    }
}

✅ 注意:  

  • 切点表达式 @annotation(...) 必须写全注解的完整类名;  
  • 方法参数中 CustomAnnotation annotation 会自动注入注解实例。

5. 启动项目,测试效果

访问:http://localhost:8080/user/1

控制台输出:

=== 日志拦截 ===
类名: cn.example.demo.UserController
方法: findUser
功能: 根据ID查找用户
标识: findUser

✅ 成功!无需修改业务代码,日志自动增强。


三、自定义注解还能做什么?

上述模式可轻松扩展至多种场景:

场景 实现思路
参数校验 @Phone@IdCard,在 AOP 中验证参数合法性
权限控制 @RequireRole("ADMIN"),拦截无权限请求
缓存操作 @CachePut(key = "#id"),自动写入 Redis
操作审计 记录谁在何时做了什么操作
限流熔断 结合 Sentinel 或自定义计数器

所有这些,底层逻辑都一样:定义注解 → AOP 拦截 → 执行增强逻辑


四、注意事项

  1. @Retention 必须是 RUNTIME,否则反射拿不到;
  2. Spring AOP 默认只对 Spring Bean 生效,确保被注解的类由 Spring 管理;
  3. 注解不能继承@Inherited 仅对类注解有效,对方法无效);
  4. 性能敏感场景慎用:AOP 本质是代理,有轻微开销。

五、总结

自定义注解不是“语法糖”,而是解耦业务与横切关注点的强大工具。

通过 “注解 + AOP” 模式,你可以:

  • 让代码更声明式(Declarative);
  • 避免重复样板代码;
  • 提升系统可维护性与扩展性。

下次当你想“在某些方法前后统一做点事”时,不妨试试自定义注解——

让代码自己描述意图,而不是堆满 if-else 和 log。


相关文章
|
13天前
|
数据采集 人工智能 安全
|
8天前
|
编解码 人工智能 自然语言处理
⚽阿里云百炼通义万相 2.6 视频生成玩法手册
通义万相Wan 2.6是全球首个支持角色扮演的AI视频生成模型,可基于参考视频形象与音色生成多角色合拍、多镜头叙事的15秒长视频,实现声画同步、智能分镜,适用于影视创作、营销展示等场景。
656 4
|
8天前
|
机器学习/深度学习 人工智能 前端开发
构建AI智能体:七十、小树成林,聚沙成塔:随机森林与大模型的协同进化
随机森林是一种基于决策树的集成学习算法,通过构建多棵决策树并结合它们的预测结果来提高准确性和稳定性。其核心思想包括两个随机性:Bootstrap采样(每棵树使用不同的训练子集)和特征随机选择(每棵树分裂时只考虑部分特征)。这种方法能有效处理大规模高维数据,避免过拟合,并评估特征重要性。随机森林的超参数如树的数量、最大深度等可通过网格搜索优化。该算法兼具强大预测能力和工程化优势,是机器学习中的常用基础模型。
350 164
|
7天前
|
机器学习/深度学习 自然语言处理 机器人
阿里云百炼大模型赋能|打造企业级电话智能体与智能呼叫中心完整方案
畅信达基于阿里云百炼大模型推出MVB2000V5智能呼叫中心方案,融合LLM与MRCP+WebSocket技术,实现语音识别率超95%、低延迟交互。通过电话智能体与座席助手协同,自动化处理80%咨询,降本增效显著,适配金融、电商、医疗等多行业场景。
359 155

热门文章

最新文章