一文搞懂:什么是注解?

简介: 一文搞懂:什么是注解?

摘自:


什么是注解?


一、概念


Java 注解是在 JDK5 时引入的新特性,注解(也被称为元数据)为我们在代码中添加信息提供了一种形式化的方法,使我们可以在稍后某个时刻非常方便地使用这些数据。注解类型定义指定了一种新的类型,一种特殊的接口类型。 在关键词 interface 前加 @ 符号也就是用 @interface 来区分注解的定义和普通的接口声明。目前大部分框架(如 Spring Boot 等)都通过使用注解简化了代码并提高的编码效率。


二、作用


提供信息给编译器: 编译器可以利用注解来探测错误和警告信息,如 @Override、@Deprecated。


编译阶段时的处理: 软件工具可以用来利用注解信息来生成代码、Html 文档或者做其它相应处理,如 @Param、@Return、@See、@Author 用于生成 Javadoc 文档。


运行时的处理: 某些注解可以在程序运行的时候接受代码的提取,值得注意的是,注解不是代码本身的一部分。如Spring 2.5 开始注解配置,减少了配置。


三、定义


2.1 注解的本质


所有的注解本质上都是继承自 Annotation 接口。但是,手动定义一个接口继承 Annotation 接口无效的,需要通过 @interface 声明注解,Annotation 接口本身也不定义注解类型,只是一个普通的接口。


public interface Annotation {


boolean equals(Object obj);


int hashCode();


String toString();


/


获取注解类型


/


Class annotationType();


}


来对比下 @interface 定义注解和继承 Annotation 接口


public @interface TestAnnotation1 {


}


?


public interface TestAnnotation2 extends Annotation {


}


通过使用 javap 指令对比两个文件的字节码,发现通过 @interface 定义注解,本质上就是继承 Annotation 接口。


// javap -c TestAnnotation1.class


Compiled from "TestAnnotation1.java"


public interface com.hncboy.corejava.annotation.TestAnnotation1 extends java.lang.annotation.Annotation {}


?


// javap -c TestAnnotation2.class


Compiled from "TestAnnotation2.java"


public interface com.hncboy.corejava.annotation.TestAnnotation2 extends java.lang.annotation.Annotation {}


虽然本质上都是继承 Annotation 接口,但即使接口可以实现多继承,注解的定义仍然无法使用继承关键字来实现。


通过 @interface 定义注解后,该注解也不能继承其他的注解或接口,注解是不支持继承的,如下代码就会报错。


public @interface TestAnnotation1 {


}


/ 错误的定义,注解不能继承注解 /


@interface TestAnnotation2 extends TestAnnotation1 {


}


/** 错误的定义,注解不能继承接口 /


@interface TestAnnotation3 extends Annotation {


}


虽然注解不支持继承其他注解或接口,但可以使用组合注解的方式来解决这个问题。如 @SpringBootApplication 就采用了组合注解的方式。


@Target(ElementType.TYPE)


@Retention(RetentionPolicy.RUNTIME)


@Documented


@Inherited


@SpringBootConfiguration


@EnableAutoConfiguration


@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),


@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })


public @interface SpringBootApplication {


}


2.2 注解的架构


?


?


注解的基本架构如图所示,先简单了解下该架构,后面会详细讲解。


该架构的左半部分为基本注解的组成,一个基本的注解包含了 @interface 以及 ElementType 和 RententionPolicy 这两个枚举类。


Annotation 和 ElementType 是一对多的关系


Annotation 和 RetentionPolicy 是一对一的关系


该架构的右半部分为 JDK 部分内置的标准注解及元注解。


标准注解:@Override、@Deprecated 等


元注解:@Documented、@Retention、@Target、@Inherited 等


2.3 注解的属性


注解的属性也称为成员变量,注解只有成员变量,没有方法。注解的成员变量在注解的定义中以“无形参的方法”形式来声明,其方法名定义了该成员变量的名字,其返回值定义了该成员变量的类型。


注解内的可使用的数据类型是有限制的,类型如下:


所有的基本类型(int,float,boolean 等)


String


Class


enum(@Retention 中属性的类型为枚举)


Annotation


以上类型的数组(@Target 中属性类型为枚举类型的数组)


编译器对属性的默认值也有约束。首先,属性不能有不确定的的值。也就是说,属性要么具有默认值,要么在使用注解时提供属性的值。对于非基本类型的属性,无论是在源代码中声明时,或是在注解接口中定义默认值时,都不能使用 null 为其值。因此,为了绕开这个约束,我们需要自己定义一些特殊的值,例如空字符串或负数,来表示某个属性不存在。


通过一个案例来演示下注解可使用的数据类型及默认值。


@interface Reference {


boolean contain() default false;


}


?


enum Week {


Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday


}


?


public @interface TestAnnotation {


?


/


int 基本数据类型


@return


*/


int type() default -1;


?


/


boolean 基本数据类型


@return


/


boolean status() default false;


?


/**


String 类型


@return


/


String name() default "";


?


/


Class 类型


@return


*/


Class loadClass() default String.class;


?


/


枚举类型


@return


/


Week today() default Week.Sunday;


?


//代码效果参考:http://hnjlyzjd.com/xl/wz_25446.html

/**

注解类型


@return


/


Reference reference() default @Reference(contain = true);


?


/


枚举数组类型


@return


*/


Week【】 value();


}


四、组成


我们已经了解了注解的架构,先来定义一个简单的注解。


@Target(ElementType.TYPE)


@Retention(RetentionPolicy.RUNTIME)


public @interface TestAnnotation {


}


3.1 ElementType


ElementType 枚举类型的常量为 Java 程序中可能出现注解的声明位置提供了简单的分类。这些常量用于 @Target 注解中。@Target 用于描述注解适用的范围,即注解能修饰的对象范围,通过 ElementType 的枚举常量表示。


先来看下 ElementType 该枚举类的代码。


public enum ElementType {


/


用于描述类、接口(包括注解类型)、枚举的定义


/


TYPE,


?


/


用于描述成员变量、对象、属性(包括枚举常量)


/


FIELD,


?


/


用户描述方法


/


METHOD,


?


/


用于描述参数


/


PARAMETER,


?


//代码效果参考:http://hnjlyzjd.com/hw/wz_25444.html

/

用于描述构造器


/


CONSTRUCTOR,


?


/


用于描述局部变量


/


LOCAL_VARIABLE,


?


/


用于描述注解的(元注解)


/


ANNOTATION_TYPE,


?


/


用于描述包


/


PACKAGE,


?


/


表示该注解能写在类型变量的声明语句中


@since 1.8


/


TYPE_PARAMETER,


?


/


表示该注解能写在使用类型的任何语句中(声明语句、泛型和强制转换语句中的类型)


@since 1.8


/


TYPE_USE


}


因为 Annotation 和 ElementType 是一对多的关系,所以 @Target 中可以存放数组,表示多个范围,默认所有范围。


JDK8 之前,注解只能用于声明的地方,JDK8 中添加了 TYPE_PARAMETER 和 TYPE_USE 类型注解,可以应用于所有地方:泛型、父类、接口,异常、局部变量等。举个例子,定义一个 @AnyWhere 注解,Boy 接口和 Test 类。


@Target({ElementType.TYPE_USE, ElementType.TYPE_PARAMETER})


@Retention(RetentionPolicy.RUNTIME)


public @interface AnyWhere {


}


?


public interface Boy {


}


?


public class Test extends @AnyWhere Object implements @AnyWhere Boy {


?


private @AnyWhere T test1(@AnyWhere T t) throws @AnyWhere Exception {


return t;


}


private void test2() {


Test test = new @AnyWhere Test();


@AnyWhere List list = new ArrayList();


}


}


3.2 RetentionPolicy


RetentionPolicy 枚举类型的常量用于保留注解的各种策略,即该注解的有效期。它们与 @Retention 注解类型一起使用,以指定保留注解的时间。RetentionPolicy 枚举的代码如下。


public enum RetentionPolicy {


/**


表示该注解只存在于源码阶段,


/


SOURCE,


?


/**


表示该注解存在于源码阶段和编译后的字节码文件里


/


CLASS,


?


/**


表示该注解存在于源码阶段、编译后的字节码文件和运行时期,且注解的内容将会被 JVM 解释执行


该范围的注解可通过反射获取到


/


RUNTIME


}


Annotation 和 RetentionPolicy 是一对一的关系,即每个注解只能有一种保留策略。


这三个枚举值是有等级关系的,SOURCE < CLASS < RUNTIME,即 RUNTIME 的有效范围是最大的,其次的是 CLASS,最小的范围是 SOURCE,默认的保留范围为 CLASS。


RUNTIME 范围使用于在运行期间通过反射的方式去获取注解。


CLASS 适用于编译时进行一些预处理操作。


SOURCE 适用于一些检查性的工作,或者生成一些辅助的代码,如 @Override 检查重写的方法,Lombok 中的 @Date、@Getter、@Setter 注解。


3.3 注解与反射


通过前面我们了解到,注解本质上继承 Annotation 接口,也就是说,Annotation 接口是所有注解的父接口。@Retention 的保留策略为 RetentionPolicy.RUNTIME 的情况下,我们可以通过反射获取注解的相关信息。Java 在 java.lang.reflect 包下也提供了对注解支持的接口。


?


?


主要来了解下 AnnotationElement 这个接口,其他接口都为该接口的子接口。该接口的对象代表 JVM 运行期间使用注解的类型(Class,Method,Field 等)。该包下的 Constructor 类、Method 类、Package 类和 Class 类等都实现了该接口。简单了解下该接口的部分函数。


public interface AnnotatedElement {


/


default 方法是 Java8 新增的


如果指定类型的注解存在该类型上,则返回 true,否则返回 false。此方法的主要目的是方便访问一些已知的注解



@param annotationClass 该泛型参数表示所有继承了Annotation 接口的接口,也就是注解


@return 返回该类型上是否有指定的注解


/


default boolean isAnnotationPresent(Class annotationClass) {


return getAnnotation(annotationClass) != null;


}


/


根据注解的 Class 查询注解


/


T getAnnotation(Class annotationClass);


/


返回该类型上的所有注解,包含继承的


/


Annotation【】 getAnnotations();


/


返回该类型上的所有注解,不包含继承的


/


Annotation【】 getDeclaredAnnotations();


}


我们使用代码来测试下反射获取注解。定义两个注解,一个保留策略为 RetentionPolicy.RUNTIME,另一个为 RetentionPolicy.CLASS。创建 TestAnnotation 类测试注解,该类上使用了这两个注解。


@Retention(RetentionPolicy.RUNTIME)


public @interface TestAnnotation1 {


String status() default "hncboy";


}


?


@Retention(RetentionPolicy.CLASS)


public @interface TestAnnotation2 {


String value() default "hncboy";


}


?


@TestAnnotation1(status = "hncboy2")


@TestAnnotation2("hncboy2")


public class TestAnnotation {


?


public static void main(String【】 args) throws ClassNotFoundException {


Class clazz = Class.forName("com.hncboy.corejava.annotation.TestAnnotation");


// 获取该类的所有注解


Annotation【】 annotations = clazz.getAnnotations();


for (Annotation annotation : annotations) {


System.out.println(annotation.annotationType());


System.out.println(annotation.toString());


}


}


}


输出结果如下,可见 TestAnnotation2 注解没有输出,因为 TestAnnotation2 注解类型是 RetentionPolicy.CLASS 的,所以用反射方法获取不到。这里还涉及到了注解的一个快捷方法,就是当注解里的属性名字定义为 value 时,可以在使用该注解时不指定属性名,上面的 @Target 注解和 @Retention 注解都属于这种情况,不过当注解里有多个属性时,那就必须指定属性名了。


interface com.hncboy.corejava.annotation.TestAnnotation1


@com.hncboy.corejava.annotation.TestAnnotation1()(status=hncboy2)


五、元注解


元注解即注解的注解且只能作用于注解上的注解,也就是说元注解负责其他注解的注解,而且只能用在注解上面。


JDK8 以前内置的元注解有 @Documented、@Retention、@Target、@Inherited 这四个,JDK 8 引入了 @Repeatable, 前面已经了解过了 @Target 和 @Retention,下面做一些简单的补充。


元注解的 @Target 都为 ElementType.ANNOTATION_TYPE,因为元注解只能应用于注解的注解。元注解在定义该注解的同时也可以直接使用该注解。


5.1 @Target


该注解用于定义注解能使用的范围,取值为 ElementType 枚举。


@Documented


@Retention(RetentionPolicy.RUNTIME)


@Target(ElementType.ANNOTATION_TYPE)


public @interface Target {


/


返回可以应用注解类型的各种范围的枚举数组


名字为 value 时可以省略属性名


@return


/


ElementType【】 value();


}


使用方式:


@Target(ElementType.METHOD)


@Target(value = ElementType.METHOD)


@Target({ElementType.METHOD, ElementType.TYPE})


@Target(value = {ElementType.METHOD, ElementType.TYPE})


5.2 @Retention


该注解定义注解的保留策略或者说定义注解的有效期,取值范围为 RetationPolicy 枚举。


@Documented


@Retention(RetentionPolicy.RUNTIME)


@Target(ElementType.ANNOTATION_TYPE)


public @interface Retention {


/


返回保留策略


@return


/


RetentionPolicy value();


}


使用方式:


@Retention(RetentionPolicy.RUNTIME)


@Retention(value = RetentionPolicy.RUNTIME)


5.3 @Documented


该注解的使用表示是否包含在生成的 javadoc 文档中。


@Documented


@Retention(RetentionPolicy.RUNTIME)


@Target(ElementType.ANNOTATION_TYPE)


public @interface Documented {


}


举个例子,定义一个 @TestAnnotation 注解和 Test 类。


@Retention(value = RetentionPolicy.RUNTIME)


@Documented


public @interface TestAnnotation {


}


@TestAnnotation


public class Test {


}


通过 javadoc -d doc .java 命令将该目录下的这两个类生成文档并放在 doc 目录下。生成的文件如下,点击 index.html。


?


?


看到如图所示的样子,Test 类中包含 @TestAnnotation。


?


?


我们再把 @TestAnnotation 注解上的 @Documenet 注解注释掉再来生成下文档。此时发现 Test 类中没有 @TestAnnotation 注解了。


?


?


5.4 @Inherited


该注解表示注解是否具有继承的特性。


@Documented


@Retention(RetentionPolicy.RUNTIME)


@Target(ElementType.ANNOTATION_TYPE)


public @interface Inherited {


}


举个例子来测试下。新建 TestAnnotation 注解,Father 类,Son 类,Father 类使用了该注解,Son 类继承 Father 类。


@Retention(RetentionPolicy.RUNTIME)


@Inherited


public @interface TestAnnotation {


}


@TestAnnotation


public class Father {


}


public class Son extends Father {


}


新建一个测试类,测试 Father 和 Son 这两个类是否包这两个注解。


public class Test {


public static void main(String【】 args) {


System.out.println(Father.class.isAnnotationPresent(TestAnnotation.class));


System.out.println(Son.class.isAnnotationPresent(TestAnnotation.class));


}


}


输出为 true true,当把 @TestAnnotation 注解上的 @Inherited 注解注释掉时,输出 true false,如此可见该注解的作用。


5.5 @Repeatable


JDK8 以前是不支持重复注解的,同一个地方只能使用同一个注解一次。 该注解从 JDK8 引入,该注解类型用于表示其声明注解的注解类型为可重复时。 value() 的值表示可重复注解的类型,包含注解类型。


<span class="cnblogs_code_copy

相关文章
|
2月前
|
Java 编译器 API
【面试问题】注解的实现原理?
【1月更文挑战第27天】【面试问题】注解的实现原理?
|
2月前
|
安全 Java 编译器
Java注解详解和自定义注解实战,用代码讲解
Java注解详解和自定义注解实战,用代码讲解
136 0
|
安全 Java 大数据
一文搞懂什么是“注解”
一文搞懂什么是“注解”
223 0
一文搞懂什么是“注解”
|
10月前
|
缓存 Java 索引
一文读懂注解的底层原理
一文读懂注解的底层原理
|
XML 缓存 Java
Java注解怎么用
Java注解怎么用
207 0
|
缓存 Java 容器
Spring源码学习:一篇搞懂@Autowire和@Resource注解的区别
最近在刷到很多文章讲解Spring IOC依赖注入时@Autowire和@Resource注解的区别,不同的文章总结出来的点有异同,所以还是看源码自己总结一下其两者的区别,及其用法。
130 0
Spring源码学习:一篇搞懂@Autowire和@Resource注解的区别
|
Java 编译器
注解和反射(一)【注解的基础知识和架构】
注解和反射(一)【注解的基础知识和架构】
105 0
注解和反射(一)【注解的基础知识和架构】
|
算法 Java 测试技术
《Spring 手撸专栏》第 15 章:万人之敌,通过注解给属性注入配置和Bean对象
实现 1. 工程结构 2. 把读取到属性填充到容器 3. 自定义属性注入注解 4. 扫描自定义注解 5. 在Bean的生命周期中调用属性注入 测试 1. 事先准备 2. 属性配置文件 3. 单元测试
268 0
《Spring 手撸专栏》第 15 章:万人之敌,通过注解给属性注入配置和Bean对象