2224 字
11 分钟
注解是什么?

@Documented
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface CacheException { }

这个 Java 代码定义了一个自定义注解,名为 @CacheException


1. 什么是注解(Annotation)?#

注解是 Java 提供的一种元数据形式,可以附加在包、类、方法、字段等程序元素上,为这些元素添加额外的信息。

  • 注解本身不直接影响代码的逻辑,但可以被编译器、开发工具或框架(如 Spring)读取并执行相应的处理。

  • 常见的注解如 @Override(标记重写方法)、@Deprecated(标记已废弃)等。


2. 解析这个 @CacheException 注解的定义#

  • @Documented:表示使用该注解的元素会被 Javadoc 或类似的工具文档化。当用 Javadoc 生成 API 文档时,要把使用了 @CacheException 的地方也写进文档里。这样别人看文档时就能知道这个方法有个特殊标记。

  • @Target(ElementType.METHOD):指定这个注解只能用于方法上(不能用于类、字段等)。如果试图在非方法元素上使用,编译器会报错。

  • @Retention(RetentionPolicy.RUNTIME):这是一种注解保留策略。指定这个注解在运行时仍然保留,可以通过反射机制读取。这样框架或 AOP 切面就能在程序运行时检测到哪些方法加了此注解。

  • public @interface CacheException:声明这是一个注解类型(使用 @interface 关键字),名字为 CacheException

注意:这个注解内部没有定义任何属性(如 String value() 等),所以它只是一个标记注解,仅用于标记方法。


3. 注解有什么用?#

(1) 给编译器提供信息#

编译器可以利用注解来检测错误或抑制警告。

  • 检查错误:最经典的例子是 @Override(在Java中)。告诉编译器这个方法是要重写父类的方法。如果父类没有这个方法,或者拼写错了,编译器就会报错,而不是把它当成一个新方法,避免了隐蔽的逻辑错误。

  • 抑制警告:比如在Java中,使用了某些老旧(弃用)的代码,编译器会发出警告。可以用 @SuppressWarnings 告诉编译器:“我知道这段代码有问题,但我是故意的,别警告我了。”

(2) 编译期或部署期的处理#

有些工具可以在编译时读取注解,生成额外的代码或文件。

  • 代码生成:像 Lombok 这个库,只需要写一个 @Getter 注解,编译器在编译时就会自动生成 getter 方法。看到的源码很简洁,但编译后的 .class 文件里包含了完整的方法。

  • 配置文件生成:以前写 EJB(企业级JavaBean)需要配置复杂的 XML 文件,现在通过注解可以直接在代码里标记,框架在部署时自动扫描并生成配置。

(3) 运行时处理(最强大的用途)#

这是注解在现代框架(如 Spring、Spring Boot)中最核心的用法。程序在运行期间(通过反射机制)可以读取注解,并根据注解进行动态逻辑处理。

  • 依赖注入:在 Spring 中,写一个 @Autowired 注解,Spring 框架在启动时就会看到这个注解,然后自动帮你把需要的对象实例赋值给这个变量。完全不用手动去 new 对象。

  • 定义路由:在写 Web 接口时,写 @RequestMapping("/user"),框架启动时就会建立一张映射表:当用户访问 /user 这个 URL 时,就自动调用这个方法来处理。

  • 事务管理:加上 @Transactional 注解,Spring 就会自动在这个方法开始前开启数据库事务,方法结束后提交事务,遇到异常时自动回滚。

(4) 作为标记#

注解可以作为一种标记,表明某个类、方法或变量的某种性质。

  • 测试标记:JUnit(Java的单元测试框架)通过 @Test 注解来标记哪些方法是测试方法。测试运行器只会执行标了 @Test 的方法,而忽略其他普通方法。

  • 线程安全标记:像 @ThreadSafe 或 @NotThreadSafe 这样的注解(通常来自 Google 的 Guava 库或 FindBugs 工具),用来标记这个类是否是线程安全的。虽然它本身不阻止错误,但它是一个重要的文档说明,也可以被代码分析工具读取,检查是否被错误使用。


4. 为什么需要这样的注解?#

  • 精细化控制:有些关键业务必须保证缓存正确性,如果缓存出错就不能继续;有些非核心业务则可以容忍缓存降级。

  • 避免吞异常:默认情况下,Spring Cache 等框架可能会在缓存异常时只记录日志,然后执行原方法,导致异常被隐藏。通过注解可以显式要求抛出异常。

  • 可读性:直接在方法上加上 @CacheException,代码意图清晰,开发者一看就知道这个方法“对缓存异常敏感”。


概念简介#

元数据#

元数据就是“描述数据的数据”。
比如你有一本书,书的内容是“数据”,而书的封面、作者、出版日期、ISBN 号等就是这本书的“元数据”。它们不直接告诉你故事内容,但描述了这本书的属性。

在编程中,代码本身是“数据”,而注解(Annotation)就是一种元数据,它附加在类、方法、字段上,描述这些代码元素的额外信息。比如:

@Override public String toString() { ... }

这里的 @Override 就是一个元数据,它告诉编译器:“这个方法打算覆盖父类的方法”。编译器可以据此检查你是否真的覆盖了父类方法。

注解就是 Java 提供的一种元数据形式,可以给代码添加标签,供工具、框架或编译器使用。


文档化#

Javadoc 工具可以从 Java 源代码中提取注释(以 /** ... */ 格式)并生成 HTML 格式的 API 文档。

当我们在自定义注解上使用 @Documented 时,意味着使用该注解的元素在生成 Javadoc 时,注解信息也会被包含在文档中

例如,你有一个类使用了 @CacheException 注解,如果没有 @Documented,生成的 Javadoc 里可能不会显示这个注解。加上 @Documented 后,API 文档中会明确标注出这个元素带有 @CacheException 注解,方便使用者查看。

如果不加 @Documented,影响就是:当别人通过 Javadoc 查看你的代码时,看不到这个注解的存在,可能会忽略某些重要的标记(比如方法有特殊行为)。但这不影响代码运行,只是影响文档的完整性。


注解保留策略#

注解有三种保留策略(RetentionPolicy):

  • SOURCE:注解只在源码中存在,编译时会被丢弃(如 @Override,编译器用完后就不需要了)。

  • CLASS:注解会保留在编译后的 .class 文件中,但 JVM 加载时不会保留(默认值,很少用)。

  • RUNTIME:注解会保留到运行时,JVM 加载类时仍然存在,可以通过反射读取。


反射机制#

反射是 Java 的一种能力,允许程序在运行时动态地获取类的信息(如它的方法、字段、注解等),并能操作这些成员(调用方法、修改字段值)。简单说,就是程序可以在运行时“自我检查”并“自我调整”。


AOP 切面#

什么是 AOP?#

AOP(Aspect-Oriented Programming,面向切面编程) 是一种编程思想,它允许将横切关注点(cross-cutting concerns)从业务逻辑中分离出来

“横切关注点”是指那些分散在程序多个地方的通用功能,比如:

  • 日志记录

  • 事务管理

  • 权限检查

  • 性能监控

  • 缓存异常处理(我们之前的例子)

传统方式下,可能需要在每个业务方法里都写一遍日志代码,导致大量重复。AOP 允许把这些通用代码抽取到一个“切面”中,然后通过“织入”的方式,在特定时机(如方法执行前、后、抛出异常时)自动执行。

AOP 能做什么?#

  • 减少重复代码:一次编写,多处应用。

  • 增强可维护性:修改通用功能只需改一处。

  • 提高模块化:业务类只关注业务逻辑,通用功能由切面负责。

与注解结合的例子#

我们之前提到的 @CacheException 就是与 AOP 结合使用的典型:

  1. 定义一个切面类,用 @Around("@annotation(cacheException)") 拦截所有带有 @CacheException 的方法。

  2. 在切面代码中,当执行原方法时捕获异常,并根据需要处理(例如直接抛出)。

  3. 这样,任何业务方法只要加上 @CacheException,就自动获得了“缓存异常时抛出”的行为,无需修改业务方法内部的代码。

注解是什么?
https://blog.heyk.cloud-ip.cc/posts/教程/后端/注解/
作者
夜雨
发布于
2026-03-03
许可协议
CC BY-NC-SA 4.0