了解注解
Java 注解(Annotation)又称 Java 标注,是 JDK5.0
引入的一种注释机制。是提供一种为程序元素设置元数据的方法,这些信息可以被编译器、开发工具或运行时环境读取和处理。
(程序元素:接口、类、属性、方法等; 元数据:描述数据的数据)
关键特性:
注解本身不包含业务逻辑,但可以通过反射机制在运行时获取并处理
注解不影响程序本身的执行,但可以通过工具影响程序的行为
注解可以包含命名参数,这些参数可以有默认值
其实就是写在接口、类、属性、方法上的一个标签,或者说是一个特殊形式的注释,普通注释只是一个注释,而注解在代码运行时是可以被反射读取并进行相应的操作,而如果没有使用反射或者其他检查,那么注解是没有任何真实作用的,也不会影响到程序的正常运行结果。
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<? 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, CLASS, RUNTIME }
|
其中:
- 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
|
@Retention(RetentionPolicy.RUNTIME) @interface MyAnnotation { String[] value() default "unknown"; }
class Person {
@MyAnnotation @Deprecated public void empty(){ System.out.println("\nempty"); }
@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 = new Person(); Class<Person> c = Person.class; Method mSomebody = c.getMethod("somebody", new Class[]{String.class, int.class}); mSomebody.invoke(person, new Object[]{"lily", 18}); iteratorAnnotations(mSomebody);
Method mEmpty = c.getMethod("empty", new Class[]{}); mEmpty.invoke(person, new Object[]{}); iteratorAnnotations(mEmpty); } public static void iteratorAnnotations(Method method) {
if(method.isAnnotationPresent(MyAnnotation.class)){ MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class); 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取值为SOURCE
→CLASS
→RUNTIME
。 -
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
| @Retention(RetentionPolicy.SOURCE) public @interface Getter { AccessLevel value() default AccessLevel.PUBLIC; }
@Getter public class User { private String name; }
|
@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
|
@Retention(RetentionPolicy.RUNTIME) @Target({ElementType.TYPE, ElementType.METHOD}) public @interface MyAnnotation { String description() default ""; }
@MyAnnotation(description = "这是一个类级别的注解示例") public static class AnnotationTest { 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 @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited { }
|
当一个类使用了带有 @Inherited
的注解时,这个注解会被该类的子类继承。
- 仅对类注解有效:
@Inherited
只对类级别的注解有效(即注解的 @Target
包含
ElementType.TYPE
)
- 不适用于接口:从父类继承的注解不会应用于实现接口的类
- 不适用于方法等其他元素:方法上的注解不会被继承
示例,通过反射获取注解
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 { }
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[] annotations = MyClass.class.getAnnotationsByType(MyAnnotation.class); for (MyAnnotation annotation : annotations) { System.out.println(annotation.value()); } MyAnnotations container = MyClass.class.getAnnotation(MyAnnotations.class); if (container != null) { 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文档里。
根据 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 @Retention(RetentionPolicy.RUNTIME)
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(); }
|
@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!"); } }
|
编译时会检查是否确实是函数式接口
如果不是(有多个抽象方法),会报错