自定义注解实战:用 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。


相关文章
|
Linux Shell
在Linux中如何一次性运行多个命令?
在Linux中如何一次性运行多个命令?
1068 0
BAT中取得一个命令的执行结果
BAT中取得一个命令的执行结果
1602 0
|
5月前
|
存储 NoSQL 关系型数据库
十年大厂员工终明白:MySQL性能优化的尽头,是对B+树的极致理解
存储引擎是数据库的核心组件,负责数据的存储与管理。常见存储引擎如MySQL的InnoDB采用B+树结构,以优化读取性能,支持高效查询、范围检索和有序遍历。相比哈希表和B树,B+树通过减少I/O次数,提升大规模数据下的查询效率。本文深入解析B+树的原理、优势及其在MySQL中的应用。
271 0
|
10月前
|
存储 人工智能 自然语言处理
RAG 调优指南:Spring AI Alibaba 模块化 RAG 原理与使用
通过遵循以上最佳实践,可以构建一个高效、可靠的 RAG 系统,为用户提供准确和专业的回答。这些实践涵盖了从文档处理到系统配置的各个方面,能够帮助开发者构建更好的 RAG 应用。
4526 115
|
SQL Java 数据库连接
【问题解决】nested exception is org.apache.ibatis.exceptions.TooManyResultException:Expected one result
【问题解决】nested exception is org.apache.ibatis.exceptions.TooManyResultException:Expected one result
|
运维 Java Serverless
深度解析四大主流软件架构模型:单体架构、分布式应用、微服务与Serverless的优缺点及场景应用
深度解析四大主流软件架构模型:单体架构、分布式应用、微服务与Serverless的优缺点及场景应用
1802 0
|
SQL 人工智能 移动开发
Android应用启动流程:从启动到可交互的过程解析
Android应用启动流程:从启动到可交互的过程解析
|
前端开发
【Netty 网络通信】ChannelFuture 解析
【1月更文挑战第9天】【Netty 网络通信】ChannelFuture 解析
|
Java API Android开发