Skip to content

What(注解) ​

我们都知道在Java代码中使用注释是为了提升代码的可读性。即注释是给人看的(对于编译器来说没有意义)。注解可以看做是注释的“强力升级版"。它可以向编译器、虚拟机等解释说明一些事情(即它对编译器等工具也是“可读”的)。比如我们熟悉的@Override注解,它的作用是告诉编译器它所注解的方法是重写的父类中的方法。这样编译器就会去检查父类是否存在这个方法,以及这个方法的签名与父类是否相同。

即注解是描述Java代码的代码,它能够被编译器解析,注解处理工具在运行时也能够解析注解。除了向编译器等传递一些信息,我们也可以使用注解生成代码。比如我们可以使用注解来描述我们的意图。然后让注解解析工具来解析注解,以此来生成一些”模板化“的代码。比如Hibernate、Spring等框架大量使用了注解,来避免一些重复的工作。注解是一种”被动“的信息,必须由编译器或虚拟机来“主动”解析它,它才能发挥自己的作用。

注解(Annotation)提供了一种安全的类似注释的机制,为我们在代码中添加信息提供了一种形式化得方法。使我们可以在稍后某个时刻方便的使用这些数据(通过解析注解来使用这些数据)。用来将任何的信息或者元数据与程序元素(类、方法、成员变量等)关联。就是更加直观更加明了的说明。这些说明信息与程序业务逻辑没有关系,并且是供指定的工具或框架使用的。Annotation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的申明语句中。

Annotation是一种接口。通过Java的反射机制相关的API来访问Annotation信息。相关类(框架或工具中的类)根据这些信息来决定如何使用该程序元素或改变它们的行为。Java语言解释器在工作时会忽略这些Annotation。因此在JVM中这些Annotation是“不起作用”的,只能通过配套的工具才能访问和处理这些Annotation类型的信息。

Annotation和interface的异同 ​

  • annotition的类型使用关键字@interface而不是interface。它继承了java.lang.annotition.Annotition接口,并非申明了一个interface。

  • Annotation类型、方法定义是独特的、受限制的。Annotation类型的方法必须申明为无参数、无异常抛出的。这些方法定义了Annotation的成员:方法名称为了成员名,而方法返回值称为了成员的类型。而方法返回值必须为primitive类型、Class类型、枚举类型、Annotation类型或者由前面类型之一作为元素的一位数组。方法的后面可以使用default和一个默认数值来申明成员的默认值,null不能作为成员的默认值。这与我们在非Annotation类型中定义方法有很大不同。Annotation类型和他的方法不能使用Annotation类型的参数,成员不能是generic。只有返回值类型是Class的方法可以在Annotation类型中使用generic。因为此方法能够用类转换将各种类型转换为Class。

  • Annotation类型又与接口有着近似之处。它们可以定义常量、静态成员类型(比如枚举类型定义)。Annotation类型也可以如接口一般被实现或者继承。

一、注解的本质:继承 Annotation 的接口 ​

@interface 只是语法糖。javac 编译后,注解就是一个继承了 java.lang.annotation.Annotation 的接口,反编译可见:

java
// 源码                          // 编译产物(反编译)
@Override                 ==>    public @interface Override { ... }
                                 // 实际上是:
public @interface Override {}    public interface Override extends Annotation {}
  • 所以注解"没有实现体"——它只是贴在程序元素上的元数据,本身不含逻辑。这也解释了上文"注解是被动信息"的说法;

  • 注解的成员 = 接口的"无参方法":String value(); 声明成员 value,default 给默认值,类型限制即上文列出的那几类;

  • 使用注解等价于让编译器生成一个实现了该接口的代理实例(运行期由 JDK 动态代理生成)。反射读注解拿到的就是这个代理对象。所以注解与 JDK动态代理源码分析 在底层是同一套机制。

二、元注解:给注解定规矩的注解 ​

自定义注解前先想清楚四个元注解(详见 Java元注解):

元注解管什么高频取值
@Target能贴在哪些位置TYPE、METHOD、FIELD、PARAMETER
@Retention保留到什么阶段SOURCE、CLASS、RUNTIME
@Documented是否进 javadoc无参数,标了就进
@Inherited子类能否继承类上的注解只对 class 的 extends 生效
  • @Retention 三档对应的经典注解:@Override 是 SOURCE(编译器检查完就丢)、@FunctionalInterface 是 SOURCE。Spring 的 @Component 是 RUNTIME(容器启动反射扫描要用)。框架做代码生成(如 Lombok)常用 CLASS/注解处理器在编译期处理。

三、运行时怎么读取注解 ​

只有 @Retention(RetentionPolicy.RUNTIME) 的注解才能在运行期反射读到——这是 Spring "扫包注入" 的地基:

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyComponent { String value() default ""; }

@MyComponent("orderService")
public class OrderService { }

// 运行期读取
MyComponent anno = OrderService.class.getAnnotation(MyComponent.class);
boolean has = OrderService.class.isAnnotationPresent(MyComponent.class);
String name = anno.value();               // "orderService"
  • 读方法/字段上的注解用 Method/Field 的同名 API;参数注解用 method.getParameterAnnotations();

  • 只查"自己声明的"用 getDeclaredAnnotation(不含继承来的),getAnnotation 会沿 @Inherited 向父类找——两者差异见 Java注解的继承;

  • 注意:注解继承有严格规则:@Inherited 只对类生效,方法被重写、接口上的注解都会“断链”。Spring 里 @Transactional “看起来能继承”多数是框架自己向上扫描的结果。

四、注解能干什么:三种消费方式 ​

  • 编译期检查:@Override、@FunctionalInterface,编译器直接消费,零运行时成本;

  • 编译期生成代码:注解处理器(APT)扫描注解生成新源码,Lombok、MapStruct、Dagger 的原理;

  • 运行期反射驱动框架:Spring 的 @Component/@Autowired、MyBatis 的 @Select、JUnit 的 @Test——"注解 + 反射 + 动态代理"三件套就是主流框架的骨架。

一句话总结:注解 = 贴在代码上的、可被工具读取的类型化元数据,本质是继承 Annotation 的接口。Retention 决定它能活到哪个阶段,RUNTIME 注解 + 反射构成了几乎所有框架的入口。

2026-10 扩充(Round1 查漏)。