Annotation
1、简介
Annotation(注解)是JDK5开始引入的新特性,可以看作是一种特殊的注释,主要用于修饰类,方法或者变量,在框架中大量使用(如 Spring、Mybatis等)
注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。
下面是我简单写的一个自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Test {
public int id() default -1;
public String msg() default "Hi";
}
通过对上述文件的字节码(.class)的反编译(javap - p xxx.class)可以得到@interface其实就是一个继承了Annotation的一个接口
public interface com.dyw.annotation.Test extends java.lang.annotation.Annotation {
public abstract int id();
public abstract java.lang.String msg();
}
注解只有被解析之后才会生效,常见的解析方法有两种:
- 编译期间直接扫描:编译器在编译Java代码的时候扫描对于的注解并处理,比如某个方法使用了@Override,编译器在编译的时候就会检测当前的方法是否重写了父类对于的方法。
- 运行期间通过反射处理:这个经常在Spring框架中看到,例如Spring的@Value注解,就是通过反射来进行处理的。
注解详细介绍
我们通过上述的例子可以看到我们的注解上面还有着其他的注解例如@Retention、@Target(这些都统称为元注解).
所以一个注解是由以下成分组成
- 元注解
- public @interface 注解名称`
2、元注解介绍
JDK1.8版本为我们提供了6个标准的用来对注解类型进行注解的注解类(1.8之前只有四个),我们称之为meta-annotation(元注解).
元注解只能用在注解之上(自定义注解时可用)
@Target
@Retention
@Documented
@Inherited
@Native(1.8新增)
@Repeatable(1.8新增)
2.1、@Target
官方解释:
指示注解类型适用的上下文。注解类型可能适用的声明上下文和类型上下文在 JLS 9.6.4.1 中指定,并在源代码中由java.lang.annotation.ElementType的枚举常量表示。
如果注解类型T上不存在@Target元注解,则类型T的注解可以写为除类型参数声明之外的任何声明的修饰符。
如果存在@Target元注解,编译器将强制执行ElementType枚举常量指示的使用限制,符合 JLS 9.7.4。
- 它指明了它所修饰的注解使用的范围 如果自定义的注解为含有@Target元注解修饰,那么默认可以是在(除类型参数之外的)任何项之上使用,若有@Target元注解修饰那么根据Value(ElementType枚举常量)的指定的目标进行规定。
2.2、@ElementType
ElementType.class
public enum ElementType {
/** 类、接口(包括注解类型)或枚举声明 */
TYPE,
/** 字段声明(包括枚举常量) */
FIELD,
/** 方法声明 */
METHOD,
/** 参数声明 */
PARAMETER,
/** 构造函数声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包装声明 */
PACKAGE,
/**
* 类型参数声明 类型参数即Map<String,Integer>中的String和Integer这里是作为类型
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* 使用类型 对应于 JLS 4.11 中的 15 个类型上下文,以及两个声明上下文:类型声明(包括注解类型声明)和类型参数声明。
*
* @since 1.8
*/
TYPE_USE
}
- ElementType的枚举常量指明了注解可以使用的目标。
@Target(ElementType.METHOD)//可修饰在方法之上
2.3、@Retention
官方解释:
指示要保留带注解类型的注解多长时间。如果注释类型声明中不存在保留注释,则保留策略默认为RetentionPolicy.CLASS 。
仅当元注释类型直接用于注释时,保留元注释才有效。如果将元注释类型用作另一个注释类型中的成员类型,则它没有效果。
- 即
@Retention用来约束注解的生命周期,分别有三个值,源码级别(source)、类文件级别(class)或者运行时级别(runtime)可以通过指定@Retention中的值来实现(值为RetentionPolicy枚举常量)。
RetentionPolicy.class
public enum RetentionPolicy {
/**
* 注解将被编译器丢弃。(该类型的注解信息指挥保留在源码中,源码经过编译后,注解信息会被丢弃,不会保留在编译好的class文件中)
*/
SOURCE,
/**
* 注解将由编译器记录在类文件中,但不需要在运行时由 VM 保留。这是默认行为.(该类型的注解信息会保留在源码里和class文件里,在执行的时候,不会加载到虚拟机中) 该类型也是未指定@Retention值的缺省值
*/
CLASS,
/**
* 注解将由编译器记录在类文件中,并在运行时由 VM 保留,因此可以反射性地读取它们。(源码,class文件和执行时(VM)都保留注解的信息)
*/
RUNTIME
}
注意:生命周期大小排序为SOURCE < CLASS < RUNTIME,范围依次增大,前者能使用的地方后者一定能使用。如果需要在运行时去动态获取注解信息,那只能使用RUNTIME;如果要在编译时进行一些预处理操作,比如生成一些辅助代码,就是用CLASS;如果只是做一些检查性的操作,比如@Override和@SupperssWarning,可选择SOURCE
2.4、@Documented
官方解释:
表示默认情况下,带有类型的注释将由 javadoc 和类似工具记录。这种类型应该用于注解类型的声明,这些类型的注释会影响其客户对注释元素的使用。如果使用 Documented 对类型声明进行注释,则其注释将成为注释元素的公共 API 的一部分。
- 带上该注解后的注解表明,在默认情况下这个注解是由JavaDoc和类似工具记录的,即带上了该文档化的注解被使用再生成文档时,会称为API的一部分。(默认情况下JavaDoc是不包含注解的,除非声明注解的时候使用了
@Documented)Person.java
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Person {
String name() default "";
}
main.java
public class main {
@Person(name = "ding")
public static void main(String[] args) {
System.out.println("hello");
}
//@Person(name = "ding")
@Person(name = "yang")
public static void doSome(){
}
}
- 生成的文档
- 不带
@Documented注解生成的文档
2.5、@Inherited
官方解释:
指示注解类型是自动继承的。如果注解类型声明中存在 Inherited 元注解,并且用户在类声明中查询注解类型,并且类声明没有该类型的注解,则将自动查询该类的超类以获取注解类型。将重复此过程,直到找到此类型的注释,或到达类层次结构(对象)的顶部。如果没有超类具有此类型的注释,则查询将指示所讨论的类没有此类注释。
请注意,如果注释类型用于注释类以外的任何内容,则此元注释类型无效。另请注意,此元注释仅导致注释从超类继承;已实现接口上的注解无效。
- 被该元注解修饰的自定义注解再使用后会自动继承,如果使用了该自定义注解去修饰一个class那么这个注解也会作用于该class的子类。就是说如果某个类使用了被
@Inherited修饰的注解,则其子类将会自动具有该注释 注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。
@Inherited //使用@Inherited修饰的自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Person {
String name() default "";
}
public class main {
public static void main(String[] args) {
System.out.println(Father.class.getAnnotation(Person.class));
System.out.println(Son.class.getAnnotation(Person.class));
}
}
@Person
class Father {
}
class Son extends com.dyw.annotation.obj.Father {
}
2.6、 @Native
官方解释:
表示可以从本机代码引用定义常量值的字段。注释可以被生成本机头文件的工具用作提示,以确定是否需要头文件,如果需要,它应该包含哪些声明。
- 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
2.7、 @Repeatable
官方解释:
注释类型java.lang.annotation.Repeatable用于指示它(元)注释其声明的注释类型是可重复的。 @Repeatable的值表示可重复注解类型的包含注解类型。
- 使用 @Native 注解修饰成员变量,则表示这个变量可以被本地代码引用,常常被代码生成工具使用。对于 @Native 注解不常使用,了解即可。
2.8、 @Repeatable
官方解释:
注释类型java.lang.annotation.Repeatable用于指示它(元)注释其声明的注释类型是可重复的。 @Repeatable的值表示可重复注解类型的包含注解类型。
`@Repeatable`允许在相同的程序元素中重复注解(不报错)。在需要对同一种注解多次使用时,往往需要借助 @Repeatable 注解。Java 8 版本以前,同一个程序元素前最多只能有一个相同类型的注解,如果需要在同一个元素前使用多个相同类型的注解,则必须使用注解“容器”。
不使用@Repeatable修饰的自定义注解完成重复注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Persons {
Person[] value();
}
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Person {
String name() default "";
}
@Persons(value = {@Person(name = "ding"),@Person(name = "yang")})
public static void doSome(){
}
使用@Repeatable修饰的自定义注解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Persons {
Person[] value();
}
@Repeatable(Persons.class)
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Person {
String name() default "";
}
@Person(name = "ding")
@Person(name = "yang")
public static void doSome(){
}
-
两种方法不同的地方是,创建重复注解Person时加上了@Repeatable注解,指向存储注解Persons,这样使用时就可以直接重复使用Person注解。从上述例子可以看出使用@Repeatable注解更符合常规思维,可读性强。
-
但两种方法的效果相同,只是使用了@Repeatable注解简化了写法,这种简化的底层依旧是多个重复注解使用了一个被称作“容器”注解的value的成员的数组元素处理。