java注解原理详解
1.注解的本质
「java.lang.annotation.Annotation」接口中有这么一句话,用来描述『注解』。
The common interface extended by all annotation types
所有的注解类型都继承自这个普通的接口(Annotation)
注解的本质就是一个继承了 Annotation 接口的接口
2.元注解
『元注解』是用于修饰注解的注解,通常用在注解的定义上,例如:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
JAVA 中有以下几个『元注解』:
@Target:注解的作用目标
@Retention:注解的生命周期
@Documented:注解是否应当被包含在 JavaDoc 文档中
@Inherited:是否允许子类继承该注解
2.1 @Target注解
@Target(value = {ElementType.FIELD})
被这个 @Target 注解修饰的注解将只能作用在成员字段上,不能用于修饰方法或者类。其中,ElementType 是一个枚举类型,有以下一些值:
ElementType.TYPE:允许被修饰的注解作用在类、接口和枚举上
ElementType.FIELD:允许作用在属性字段上
ElementType.METHOD:允许作用在方法上
ElementType.PARAMETER:允许作用在方法参数上
ElementType.CONSTRUCTOR:允许作用在构造器上
ElementType.LOCAL_VARIABLE:允许作用在本地局部变量上
ElementType.ANNOTATION_TYPE:允许作用在注解上
ElementType.PACKAGE:允许作用在包上
2.2 @Retention注解
@Retention(value = RetentionPolicy.RUNTIME)
这里的 RetentionPolicy 依然是一个枚举类型,它有以下几个枚举值可取:
RetentionPolicy.SOURCE:当前注解编译期可见,不会写入 class 文件
RetentionPolicy.CLASS:类加载阶段丢弃,会写入 class 文件
RetentionPolicy.RUNTIME:永久保存,可以反射获取
@Retention 注解指定了被修饰的注解的生命周期,一种是只能在编译期可见,编译后会被丢弃,一种会被编译器编译进 class 文件中,无论是类或是方法,乃至字段,他们都是有属性表的,而 JAVA 虚拟机也定义了几种注解属性表用于存储注解信息,但是这种可见性不能带到方法区,类加载时会予以丢弃,最后一种则是永久存在的可见性。
2.3 @Documented注解
当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。
2.4 @Inherited注解
@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。
3.JAVA 的内置三大注解
除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:
@Override:表示当前的方法定义将覆盖父类中的方法
@Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
@SuppressWarnings:表示关闭编译器警告信息
3.1 @Override注解
@Override 注解想必是大家很熟悉的了,它的定义如下:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
3.2@Deprecated
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
依然是一种『标记式注解』,永久存在,可以修饰所有的类型,作用是,标记当前的类或者方法或者字段等已经不再被推荐使用了,可能下一次的 JDK 版本就会删除。
3.3 @SuppressWarnings
它有一个 value 属性需要你主动的传值,这个 value 代表一个什么意思呢,这个 value 代表的就是需要被压制的警告类型。例如:
public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}
这么一段代码,程序启动时编译器会报一个警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已过时
而如果我们不希望程序启动时,编译器检查代码中过时的方法,就可以使用@SuppressWarnings 注解并给它的 value 属性传入一个参数值来压制编译器的检查。
@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2018, 7, 11);
}
这样你就会发现,编译器不再检查 main 方法下是否有过时的方法调用,也就压制了编译器对于这种警告的检查.当然,JAVA 中还有很多的警告类型,他们都会对应一个字符串,通过设置 value 属性的值即可压制对于这一类警告类型的检查。
4.自定义注解
定义的格式如下:
[@Target]
[@Retention]
[@Documented]
[@Inherited]
public @interface [名称] {
// 元素
}
形式跟接口很类似,不过前面多了一个@符号。
下面的创建了一个名为Test的注解,你可以简单理解为创建了一张名字为Test的标签。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
}
定义注解时可给注解添加属性,也叫注解的成员变量。注解只有成员变量,没有方法。
注解的成员变量的以“无形参的方法”形式来声明。
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value();
}
还可给注解的属性设定默认值
@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
String value() default "hello";
}
注解的属性可支持数据类型有如下:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
5.自定义注解的使用
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface MyAnno {
String fieldName();
boolean isPrimaryKey() default false;
}
在使用的时候,必须给没有设置默认值的属性赋值。赋值的方式是在注解的括号内以 value=”” 形式进行赋值。
public class Person {
private int id;
@MyAnno(fieldName = "name")
private String name;
}
public class Person {
@MyAnno(fieldName = "id", isPrimaryKey = true)
private int id;
@MyAnno(fieldName = "name", isPrimaryKey = true)
private String name;
}
6.注解
从原理上讲,注解处理器就是通过反射机制获取被检查方法上的注解信息,然后根据注解元素的值进行特定的处理。
public static void main(String[] args) {
List<Integer> useCases = new ArrayList<Integer>();
Collections.addAll(useCases, 47, 48, 49, 50);
trackUseCases(useCases, PasswordUtils.class);
}
public static void trackUseCases(List<Integer> useCases, Class<?> cl) {
for (Method m : cl.getDeclaredMethods()) {
UseCase uc = m.getAnnotation(UseCase.class);
if (uc != null) {
System.out.println("Found Use Case:" + uc.id() + " "
+ uc.description());
useCases.remove(new Integer(uc.id()));
}
}
for (int i : useCases) {
System.out.println("Warning: Missing use case-" + i);
}
}
Found Use Case:47 Passwords must contain at least one numeric
Found Use Case:48 no description
Warning: Missing use case-49
Warning: Missing use case-50
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。