了解注解

Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。是提供一种为程序元素设置元数据的方法,这些信息可以被编译器、开发工具或运行时环境读取和处理。

(程序元素:接口、类、属性、方法等; 元数据:描述数据的数据)

关键特性:

  1. 注解本身不包含业务逻辑,但可以通过反射机制在运行时获取并处理

  2. 注解不影响程序本身的执行,但可以通过工具影响程序的行为

  3. 注解可以包含命名参数,这些参数可以有默认值

其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,普通注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。

Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。

在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。

可以利用注解来自定义一些实现,比如在某个方法上加一个自定义注解,就可以实现方法日志的自动记录打印

举例@Override:

它的作用是告诉阅读者(开发人员、编译器)这个方法重写了父类的方法,对于开发人员只是一个标志,然而编译器如果发现方法标注了这个注解,就会检查这个方法到底是不是真的覆写了父类的方法,如果没有会报错,而如果不添加@Override注解,程序也是可以正常运行的,不过缺乏了静态的检查

在spring框架中加注的注解会影响到程序的运行,是因为spring内部使用反射操作了对应的注解。

注意:注解不能直接干扰程序代码的运行

注解的作用

话说当年非常流行xml配置的。优点呢就是整个项目的配置信息集中在一个文件中,从而方便管理,是集中式的配置。缺点也显而易见,当配置信息非常多的时候,配置文件会变得越来越大不易查看管理,特别是多人协作开发时会导致一定的相互干扰。

现在都提倡解耦、轻量化或者说微小化,那么注解就顺应了这一需求,各个包或模块在内部方法或类上使用注解即可实现指定功能,缺点呢就是不方便统一管理,如果需要修改某一类功能,则需要整体搜索逐个修改,是分散式的存在各个角落

xml的方式是集中式的元数据,不需要和代码绑定的,而注解是一种分散式的元数据设置方式

开发者的视角可以解读出这个类/方法/属性的作用以及该怎么使用,而从框架的视角则可以解析注解本身和其属性实现各种功能,编译器的角度则可以进行一些预检查(@Override)和抑制警告(@SuppressWarnings)等。

  • 作为特定标记,用于告诉编译器一些信息
  • 编译时动态处理,如动态生成代码
  • 运行时动态处理,作为额外信息的载体,如获取注解信息

Annotation架构

Java 定义了一套注解,共有 7 个,3 个在 java.lang 中,剩下 4 个在 java.lang.annotation 中。

img
img

其中

  • 1 个 Annotation 和 1 个 RetentionPolicy 关联。
    • 可以理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性。
      • RetentionPolicy 是一个枚举类型,它定义了注解的保留策略,也就是规定了注解在什么阶段是可用的。
  • 1 个 Annotation 和 1~n 个 ElementType 关联。
    • 对于每 1 个 Annotation 对象,可以有若干个 ElementType 属性
  • Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override 等等。
    • 每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。

Annotation 组成部分

java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:Annotation ElementType RetentionPolicy

1
2
3
4
5
6
7
8
9
10
package java.lang.annotation;
public interface Annotation {
// 当两个注解对象满足以下条件时,认为它们相等:注解类型相同 所有注解元素的值都相等
boolean equals(Object obj);
int hashCode();
// 将当前注解对象转换为字符串表示形式,字符串表示通常包含注解的类型名称和所有注解元素的值。
String toString();
// 获取当前注解对象的注解类型。注解类型是一个 Class 对象,它表示定义该注解的接口。
Class<? extends Annotation> annotationType();
}
1
2
3
4
5
6
7
8
9
10
11
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
1
2
3
4
5
6
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /*源码级 Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 */
CLASS, /*类文件级 编译器将Annotation存储于类对应的.class文件中。默认行为 */
RUNTIME /*运行级 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

其中:

  • Annotation这个接口中,每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。
  • ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途
  • 就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。

Annotation 通用定义

1
2
3
4
5
@Documented
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation1 {
}

上面的作用是定义一个 Annotation,它的名字是 MyAnnotation1。

定义了 MyAnnotation1 之后,我们可以在代码中通过 “@MyAnnotation1” 来使用它。 其它的,@Documented, @Target, @Retention, @interface 都是来修饰 MyAnnotation1 的。

java常用的Annotation

1
2
3
4
5
6
7
@Deprecated  -- @Deprecated 所标注内容,不再被建议使用。
@Override -- @Override 只能标注方法,表示该方法覆盖父类中的方法。
@Documented -- @Documented 所标注内容,是否包含在用户文档中javadoc
@Inherited -- @Inherited只能被用来标注“Annotation类型”,它所标注的Annotation具有继承性。
@Retention -- @Retention只能被用来标注“Annotation类型”,用来指定Annotation的RetentionPolicy属性作用域。
@Target -- @Target只能被用来标注“Annotation类型”,而且它被用来指定Annotation的ElementType属性。
@SuppressWarnings -- @SuppressWarnings 所标注内容产生的警告,编译器会对这些警告保持静默。

Annotation 的作用

编译检查

Annotation 具有”让编译器进行编译检查的作用”。

例如

  • @SuppressWarnings, @Deprecated@Override 都具有编译检查作用。
  • 若某个方法被 @Override 的标注,则意味着该方法会覆盖父类中的同名方法。如果有方法被 @Override 标示,但父类中却没有”被 @Override 标注”的同名方法,则编译器会报错。
img

我们可以发现 getString() 函数会报错。这是因为 getString()@Override 所标注,但在OverrideTest 的任何父类中都没有定义 getString() 函数”。

反射中使用Annotation

在反射的 Class, Method, Field 等函数中,有许多于 Annotation 相关的接口。我们可以在反射中解析并使用 Annotation。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/**
* Annotation在反射函数中的使用示例
*/
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String[] value() default "unknown";
}

/**
* Person类。它会使用MyAnnotation注解。
*/
class Person {

/**
* empty()方法同时被 "@Deprecated" 和 "@MyAnnotation(value={"a","b"})"所标注
* (01) @Deprecated,意味着empty()方法,不再被建议使用
* (02) @MyAnnotation, 意味着empty() 方法对应的MyAnnotation的value值是默认值"unknown"
*/
@MyAnnotation
@Deprecated
public void empty(){
System.out.println("\nempty");
}

/**
* sombody() 被 @MyAnnotation(value={"girl","boy"}) 所标注,
* @MyAnnotation(value={"girl","boy"}), 意味着MyAnnotation的value值是{"girl","boy"}
*/
@MyAnnotation(value={"girl","boy"})
public void somebody(String name, int age){
System.out.println("\nsomebody: "+name+", "+age);
}
}

public class AnnotationTest {

public static void main(String[] args) throws Exception {

// 新建Person
Person person = new Person();
// 获取Person的Class实例
Class<Person> c = Person.class;
// 获取 somebody() 方法的Method实例
Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class});
// 执行该方法
mSomebody.invoke(person, new Object[]{"lily", 18});
iteratorAnnotations(mSomebody);


// 获取 somebody() 方法的Method实例
Method mEmpty = c.getMethod("empty", new Class[]{});
// 执行该方法
mEmpty.invoke(person, new Object[]{});
iteratorAnnotations(mEmpty);
}

public static void iteratorAnnotations(Method method) {

// 判断 somebody() 方法是否包含MyAnnotation注解
if(method.isAnnotationPresent(MyAnnotation.class)){
// 获取该方法的MyAnnotation注解实例
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
// 获取 myAnnotation的值,并打印出来
String[] values = myAnnotation.value();
for (String str:values)
System.out.printf(str+", ");
System.out.println();
}

// 获取方法上的所有注解,并打印出来
Annotation[] annotations = method.getAnnotations();
for(Annotation annotation : annotations){
System.out.println(annotation);
}
}
}

注解的分类

通常来说注解分为以下三类

  • 元注解 – java内置的注解,标明该注解的使用范围、生命周期等。
  • 标准注解 – Java提供的基础注解,标明过期的元素/标明是复写父类方法的方法/标明抑制警告。
  • 自定义注解 – 第三方定义的注解,含义和功能由第三方来定义和实现。

元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。

元注解就是最基本不可分解的注解,我们不能去改变它只能使用它来定义自定义的注解

元注解包含以下五种: @Retention@Target@Documented@Inherited@Repeatable

@Retention

定义

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
RetentionPolicy value();
}

标明自定义注解的生命周期,这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。它决定了注解在何时有效以及在何处可用。

从编写Java代码到运行主要周期为源文件Class文件运行时数据@Retention则标注了自定义注解的信息要保留到哪个阶段,分别对应的value取值为SOURCECLASSRUNTIME。 - SOURCE:源代码java文件,不会包含在编译后的 class 文件中,编译时候会丢弃 - CLASS:类文件级别保留,class文件中会保留注解,但是jvm加载运行时就没有了,是默认的保留策略 - RUNTIME:运行时,如果想使用反射获取注解信息,则需要使用RUNTIME,反射是在运行阶段进行反射的

各个生命周期的用途:

  • SOURCE级别
    • 典型应用:Lombok的@Getter@Setter
    • 特点:编译后完全消失,不会增加运行时负担
    • 使用场景:仅用于编译期检查或代码生成
  • CLASS级别
    • 典型应用:Android的@Keep注解
    • 特点:保留到class文件但不会被加载到JVM
    • 使用场景:字节码分析工具使用
  • RUNTIME级别
    • 典型应用:Spring的@Controller@Service
    • 特点:可通过反射获取,影响运行时行为
    • 使用场景:框架开发中最常用
1
2
3
4
5
6
7
8
9
10
11
12
// Lombok的@Getter示例 SOURCE级别
@Retention(RetentionPolicy.SOURCE)
public @interface Getter {
AccessLevel value() default AccessLevel.PUBLIC;
}

// 使用
@Getter
public class User {
private String name;
}
// 编译时会生成getName()方法,但注解本身不会保留

@Target

描述自定义注解的使用范围,允许自定义注解标注在哪些Java元素上(类、方法、属性、局部属性、参数…)

1
2
3
4
5
6
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
ElementType[] value();
}

value是一个数组,可以有多个取值,说明同一个注解可以同时用于标注在不同的元素上。value的取值如下

@Target可以指定更精确的应用目标,包括:

1
2
3
4
5
6
7
8
9
10
11
12
@Target({
ElementType.TYPE, // 类、接口、枚举
ElementType.FIELD, // 字段(包括枚举常量)
ElementType.METHOD, // 方法
ElementType.PARAMETER, // 方法参数
ElementType.CONSTRUCTOR, // 构造器
ElementType.LOCAL_VARIABLE, // 局部变量
ElementType.ANNOTATION_TYPE, // 注解类型
ElementType.PACKAGE, // 包
ElementType.TYPE_PARAMETER, // 类型参数(Java 8+)
ElementType.TYPE_USE // 类型使用(Java 8+)
})

自定义一个注解@MyAnnotation1想要用在类或方法上,就可以如下定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 自定义注解,使用@Target指定该注解可以应用在哪些元素上
* 这里指定了TYPE(类/接口/枚举等)和METHOD(方法)上
*/
@Retention(RetentionPolicy.RUNTIME) // 运行时保留
@Target({ElementType.TYPE, ElementType.METHOD}) // 只能用于类和方法上
public @interface MyAnnotation {
String description() default ""; // 注解属性默认值
}
/**
* 测试类,演示@Target的使用
* 因为@MyAnnotation允许用在TYPE上,所以可以用在类声明上
*/
@MyAnnotation(description = "这是一个类级别的注解示例")
public static class AnnotationTest {
// @MyAnnotation 用在属性上则会报错
public String name;

@MyAnnotation(description = "这是一个方法级别的注解示例")
public void testMethod() {
System.out.println("测试方法执行");
}
}

获取其中的注解,只能获取到类和方法上的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static void main(String[] args) {
// 获取类上的注解
MyAnnotation classAnnotation = AnnotationTest.class.getAnnotation(MyAnnotation.class);
System.out.println(classAnnotation.description());

try{
// 获取方法上的注释
Method method = AnnotationTest.class.getMethod("testMethod");
MyAnnotation annotation = method.getAnnotation(MyAnnotation.class);
System.out.println(annotation.description());

} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}

@Inherited

表示是否可以被标注类的子类继承。当注解 Inheritable 被 @Inherited 标注时,它具有继承性。否则,没有继承性。

定义如下

1
2
3
4
5
6
@Documented   // 说明该注解能出现在 javadoc 中。
@Retention(RetentionPolicy.RUNTIME) // 指定 Inherited 的策略是 RetentionPolicy.RUNTIME
@Target(ElementType.ANNOTATION_TYPE) // @Inherited 只能被用来标注 "Annotation 类型"
// 它的用来修饰 Inherited,意味着 Inherited 实现了 Annotation 接口;即 Inherited 就是一个注解。
public @interface Inherited {
}

当一个类使用了带有 @Inherited 的注解时,这个注解会被该类的子类继承。

  1. 仅对类注解有效@Inherited 只对类级别的注解有效(即注解的 @Target 包含 ElementType.TYPE
  2. 不适用于接口:从父类继承的注解不会应用于实现接口的类
  3. 不适用于方法等其他元素:方法上的注解不会被继承

示例,通过反射获取注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 定义一个可以被继承的注释
@Inherited
@Retention(RetentionPolicy.RUNTIME) // 运行时保留,可以通过反射获取
@Target(ElementType.TYPE) // 只能用于类/接口/枚举上
@interface InheritableAnnotation {
String value() default "父类注解";
}

// 定义一个不可被继承的注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface NonInheritableAnnotation {
String value() default "不可继承的注解";
}

// 父类,带有可继承和不可继承的注解
@InheritableAnnotation("这是父类的可继承注解")
@NonInheritableAnnotation("这是父类的不可继承注解")
class ParentClass {
}

// 子类,继承自ParentClass,没有显式添加任何注解
class ChildClass extends ParentClass {
}

打印注解的信息后可以发现:

1
2
3
4
5
6
类 ParentClass 上的注解:
InheritableAnnotation:
NonInheritableAnnotation:

类 ChildClass 上的注解:
InheritableAnnotation:

发现ChildClass 类没有继承 NonInheritableAnnotation 注解

@Repeatable

定义如下

1
2
3
4
5
6
7
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
// 指定包含重复注解的容器注解类型
Class<? extends Annotation> value();
}

标识某注解可以在同一个声明上使用多次。(Java 8+特性)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// 定义可重复注解
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
}

// 定义包含可重复注解的容器注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotations {
MyAnnotation[] value();
}

// 使用可重复注解
@MyAnnotation("First Annotation")
@MyAnnotation("Second Annotation")
class MyClass {
// 类的内容
}

public class RepeatableExample {
public static void main(String[] args) {
// 普通的获取类上的所有 MyAnnotation 注解
// 需要定义容器注解来存放重复注解
// 通过getAnnotationsByType()获取重复注解
MyAnnotation[] annotations = MyClass.class.getAnnotationsByType(MyAnnotation.class);
for (MyAnnotation annotation : annotations) {
System.out.println(annotation.value());
}
// 使用MyAnnotations,获取类上的 MyAnnotations 注解
MyAnnotations container = MyClass.class.getAnnotation(MyAnnotations.class);
if (container != null) { // 语法糖,相当于是返回一个MyAnnotation[]
for (MyAnnotation annotation : container.value()) {
System.out.println(annotation.value());
}
}
}
}

虽然我们标注的是多个@MyAnnotation,其实会给我们返回一个@MyAnnotations,相当于是Java帮我们把重复的注解放入了一个数组属性中

@Documented

定义如下

1
2
3
4
5
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}

标记注解是否包含在Javadoc中。被@Documented标注的注解会出现在生成的API文档里。

  • 无参数
  • 只影响文档生成
  • 常用于重要的API注解

根据 Annotation 生成帮助文档:

通过给 Annotation 注解加上 @Documented 标签,能使该 Annotation 标签出现在 javadoc 中。

标准注解

@Override

定义

1
2
3
4
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

标识方法覆写了父类或接口中的方法,提供编译时检查。

  • 仅用于方法
  • 编译时检查(非强制使用)
  • 源代码级别(不保留到class文件)
  • 可防止拼写错误导致意外创建新方法

支持的元素类型为:CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE

这个天天用,估计大伙都会都知道,很多人注解学习的起步就是@Override

@Deprecated

某个方法被 @Deprecated 标注,则该方法不再被建议使用

如果有开发人员试图使用或重写被 @Deprecated 标示的方法,编译器会给相应的提示信息

img
img

定义如下

1
2
3
4
5
@Documented  // 会出现在doc中
@Retention(RetentionPolicy.RUNTIME) // 指定 Deprecated 的策略是 RetentionPolicy.RUNTIME。
// @interface -- 它的用来修饰 Deprecated,即 Deprecated 就是一个注解
public @interface Deprecated {
}

@SuppressWarnings

让编译器对”它所标注的内容”的某些警告保持静默。

image-20250414173247356

定义如下

1
2
3
4
5
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
String[] value(); // SuppressWarnings 能指定参数
}

@FunctionalInterface

用于标识函数式接口

定义:

1
2
3
4
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

使用示例

1
2
3
4
5
6
7
8
9
@FunctionalInterface
interface Greeter {
void greet(String name);

// 可以有默认方法
default void defaultGreet() {
System.out.println("Hello, world!");
}
}

编译时会检查是否确实是函数式接口

如果不是(有多个抽象方法),会报错