说说自定义注解的场景及实现
登陆、权限拦截、日志处理,以及各种 Java 框架,如 Spring,Hibernate,JUnit 提到注解就不能不说反射,Java 自定义注解是通过运行时靠反射获取注解。实际开发中,例如我们要获取某个方法的调用日志,可以通过 AOP(动态代理机制)给方法添加切面,通过反射来获取方法包含的注解,如果包含日志注解,就进行日志记录。反射的实现在 Java 应用层面上讲,是通过对 Class 对象的操作实现的,Class 对象为我们提供了一系列方法对类进行操作。在 JVM 这个角度来说,Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目按严格的顺序紧凑的排列在 Class 文件中,里面包含了类、方法、字段等等相关数据。通过对 Class 数据流的处理我们即可得到字段、方法等数据。
以上是注解机制的骨架。下面补全三块实战中最需要的内容:注解本身的定义要素、运行时读取的完整链路、以及一个可运行的自定义注解实现。
定义注解的四个要素
// 能标在哪里:方法/类/字段/参数 |
- @Retention 是关键:SOURCE(编译期丢弃,如 @Override)、CLASS(进 class 文件但运行期不可读)、RUNTIME(反射可见)。自定义功能注解几乎总是 RUNTIME
- @Target 约束使用位置,编译期检查
- 成员类型受限:基本类型、String、Class、枚举、注解及它们的数组;default 值让使用方省略参数
运行时读取链路
注解在 Class 文件中存于 RuntimeVisibleAnnotations 属性;类加载后 JVM 把它解析为注解代理对象。应用层读取就是一条反射链:
Method m = UserService.class.getMethod("create", User.class); |
配合 AOP 时,切面拦截方法执行,从 MethodSignature 拿到 Method 再取注解——**注解负责声明”做什么”,切面负责执行”怎么做”**,声明与实现分离,这就是注解驱动的全部秘密。
一个完整的日志注解实现
|
典型场景清单
- 横切关注点:日志(如上)、权限(@PreAuthorize)、限流(@RateLimit)、重试(@Retryable)、缓存(@Cacheable)
- 框架配置元数据:Spring 的 @Service/@RequestMapping、MyBatis 的 @Select、JUnit 的 @Test——注解替代 XML 成为配置载体
- 编译期处理:Lombok 用 SOURCE 级注解 + 注解处理器(APT)在编译期改写 AST 生成 getter;这是另一条路线,运行期零开销
- 校验与文档:@NotNull、@Schema 等声明式约束
设计自定义注解的经验
- 注解只放元数据,不放逻辑;逻辑在处理器(切面/处理器/扫描器)里
- 命名用动词性或名词性短语表达意图(@OpLog、@Retryable),避免 @Config1 这类无语义命名
- 提供合理 default,让 80% 的使用场景只需写
@Xxx一个词 - 想清楚 Retention:能在编译期解决的(生成代码)不要用 RUNTIME,运行期反射是有成本的
注解的本质是”贴在代码上的结构化注释 + 一套读取它的处理器”。自定义注解不难,难的是把”声明”与”处理”的边界划干净——划干净了,业务代码里就只剩下一个个意图清晰的标记。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 非鱼小站!
评论
WalineDisqus











