JAVA 注解(Annotation)详解

时间:2020-12-19 20:15:17

先来看一个Java注解的思维脑图

JAVA 注解(Annotation)详解

一 注解(Annotation)基本概念和分类

1.1 注解(Annotation)基本概念

Annotion(注解)概念:Java 提供了一种原程序中的元素关联任何信息和任何元数据的途径和方法。概念有点难懂,等会看具体的例子。

Annotion(注解)是一个接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。

Annotation(注解)是JDK5.0及以后版本引入的。

Annotation(注解)基本的规则:Annotation不能影响程序代码的执行,无论增加、删除 Annotation,代码都始终如一的执行。

1.2 注解(Annotation)分类

按照运行机制分类:
1. 源码注解 : 注解只在源码中存在,编译成.class 文件就不存在了。
2. 编译时注解 : 注解在源码和.class文件中都存在。
3. 运行时注解 : 在运行阶段还起作用,甚至还会影响运行逻辑的注解。

按照注解来源分类:
1. JDK自带注解
2. 第三方注解
3. 自定义注解

1.3 元数据(metadata)基本概念和作用

元数据概念:元数据从metadata一词译来,就是“关于数据的数据”的意思。
元数据的功能作用
1. 编写文档:通过代码里标识的元数据生成文档(比如:你可能用过Javadoc的注释自动生成文档。这就是元数据功能的一种)。
2. 代码分析:通过代码里标识的元数据对代码进行分析。
3. 编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查。

在Java中元数据以标签的形式存在于Java代码中,元数据标签的存在并不影响程序代码的编译和执行,它只是被用来生成其它的文件或针在运行时知道被运行代码的描述信息。
  综上所述:
    第一,元数据以标签的形式存在于Java代码中。
    第二,元数据描述的信息是类型安全的,即元数据内部的字段都是有明确类型的。
    第三,元数据需要编译器之外的工具额外的处理用来生成其它的程序部件。
    第四,元数据可以只存在于Java源代码级别,也可以存在于编译之后的Class文件内部。

二 系统注解之标准注解和元注解

注解的语法比较简单,除了@符号的使用外,他基本与Java固有的语法一致。
JDK中内置三个标准注解,定义在java.lang中:
1. @Override:用于修饰此方法覆盖了父类的方法;
2. @Deprecated:用于修饰已经过时的方法;
3. @SuppressWarnnings:用于通知java编译器禁止特定的编译警告。

元注解的作用就是负责注解其他注解。
Java定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明。4个元注解如下:
1. @Target
2. @Retention
3. @Documented
4. @Inherited
先来看个例子

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface Table {
String value();
}

下面我们看一下每个元注解的作用和相应分参数的使用说明。
 

2.1 @Target

@Target说明了Annotation所修饰的对象范围:Annotation可被用于 packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

作用:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)

取值(ElementType)有:
1. CONSTRUCTOR:用于描述构造器
2. FIELD:用于描述字段声明
3. LOCAL_VARIABLE:用于描述局部变量
4. METHOD:用于描述方法
5. PACKAGE:用于描述包
6. PARAMETER:用于描述参数
7. TYPE:用于描述类、接口(包括注解类型) 或enum声明

上面的例子 注解Table 用于注解类、接口(包括注解类型) 或enum声明

2.2 @Retention

@Retention定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。使用这个meta-Annotation可以对 Annotation的“生命周期”限制。

作用:表示需要在什么级别保存该注释信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效)

取值(RetentionPoicy)有:
1. SOURCE:在源文件中有效(即源文件保留)。
2. CLASS:在class文件中有效(即class保留)。
3. RUNTIME:在运行时有效(即运行时保留)。

上面的例子 注解Table的RetentionPolicy属性值是RUNTIME,即在运行时有效。注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理。

2.3 @Documented

@Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员。

2.4 @Inherited

@Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

  注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

  当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

三 自定义注解和注解使用

定义注解格式:

public @interface 注解名 {定义体}

注解参数的可支持数据类型:

  1. 所有基本数据类型(int,float,boolean,byte,double,char,long,short)
  2. String类型
  3. Class类型
  4. enum类型
  5. Annotation类型
  6. 以上所有类型的数组

    使用注解的语法:
@<注解名><成员名1>=<成员值1>,<成员名2>=<成员值2>,.......)

@Description(des = “zhang”,anthor=“qilu”,age=20)
public String eyeColor(){
return “red"
}

自定义注解和使用注解注意事项:
1. 使用@interface 关键字定义注解。
2. 成员以无参无异常方式声明。
3. default为成员指定一个默认值。
4. 成员类型是受限的,合法的类型包括原始类型及String,Class,Annotation,enum,和以上类型的数组。
5. 如果注解只有一个成员,成员名字必须取名为value(),在使用时可以忽略成员名和赋值号(=)。
6. 注解类可以没有成员,没有成员的注解称为标志注解。

四 解析注解

基本概念:通过反射获取类,函数或者成员上的运行时注解信息,从而实现动态控制程序运行的逻辑。

java.lang.reflect 包下主要包含一些实现反射功能的工具类,扩充了读取运行时Annotation信息的能力。当一个Annotation类型被定义为运行时的Annotation后,该注解才能是运行时可见,当class文件被装载时被保存在class文件中的Annotation才会被虚拟机读取。
  AnnotatedElement 接口是所有程序元素(Class、Method和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下四个个方法来访问Annotation信息:

  方法1:<T extends Annotation> T getAnnotation(Class<T> annotationClass): 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null
  方法2:Annotation[] getAnnotations():返回该程序元素上存在的所有注解。
  方法3:boolean isAnnotationPresent(Class<?extends Annotation> annotationClass):判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false.
  方法4:Annotation[] getDeclaredAnnotations():返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注释。(如果没有注释直接存在于此元素上,则返回长度为零的一个数组。)该方法的调用者可以随意修改返回的数组;这不会对其他调用者返回的数组产生任何影响。

下面我们来看个例子 ,模拟查询数据库

//Column 注解
@Target({ElementType.FIELD})//字段声明
@Retention(RetentionPolicy.RUNTIME)//运行时存在,可以通过反射读取
public @interface Column {
String value();
}
//Table 注解
@Target({ElementType.TYPE})//类、接口(包括注解类型) 或enum声明
@Retention(RetentionPolicy.RUNTIME)//运行时存在,可以通过反射读取
public @interface Table {
String value();
}

//Filter类使用了 Table 和 Column 注解
@Table("user")
public class Filter {
@Column("id")
private int id;
@Column("userName")
private String userName;
@Column("nickName")
private String nickName;
@Column("age")
private int age;
@Column("city")
private String city;
@Column("email")
private String email;
@Column("mobile")
private String mobile;

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getUserName() {
return userName;
}

public void setUserName(String userName) {
this.userName = userName;
}

public String getNickName() {
return nickName;
}

public void setNickName(String nickName) {
this.nickName = nickName;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}

public String getEmail() {
return email;
}

public void setEmail(String email) {
this.email = email;
}

public String getMobile() {
return mobile;
}

public void setMobile(String mobile) {
this.mobile = mobile;
}
}

// 实现3个Filter实例
Filter f1 = new Filter();
f1.setId(10);//查询ID为10的用户

Filter f2 = new Filter();
f2.setUserName("lucy");

Filter f3 = new Filter();
f3.setEmail("zhangqilu@qq.com");


Log.e(TAG, "onCreate: "+query(f1) );
Log.e(TAG, "onCreate: "+query(f2) );
Log.e(TAG, "onCreate: "+query(f3) );
    //注解解析 通过反射
private String query(Filter filter){
StringBuilder stringBuilder = new StringBuilder();
//获取Class
Class c = filter.getClass();

//2. 获取talbe的名字
if(!c.isAnnotationPresent(Table.class)){//查看有没有Table 注解
return null;
}

Table table = (Table) c.getAnnotation(Table.class);
String tableName = table.value();
stringBuilder.append("select * from ").append(tableName).append(" where filter = 1");

//3 遍历所有字段
Field[] fields = c.getDeclaredFields();
for (Field field :fields){
if(!field.isAnnotationPresent(Column.class)){
continue;
}
//4 获取字段名字
Column column = field.getAnnotation(Column.class);
String columnName = column.value();

//5 获取字段值 通过反射
String fieldName = field.getName();
String getMethodName = "get"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
Object fieldValue = null;
try {
Method method = c.getMethod(getMethodName);
fieldValue = method.invoke(filter);
} catch( Exception e) {
e.printStackTrace();
}
//6 拼装sql
if(fieldValue==null||((fieldValue instanceof Integer)&&(Integer)fieldValue==0)){
continue;
}
stringBuilder.append(" and ").append(fieldName).append("=");
if(fieldValue instanceof String){
stringBuilder.append("'"+fieldValue+"'");
}else if(fieldValue instanceof Integer){
stringBuilder.append(fieldValue);
}else{//其他情况

}

}
return stringBuilder.toString();
}

控制台打印数据

03-24 17:44:09.223 1390-1390/? E/MainActivity: onCreate: select * from user where filter = 1 and id=10
03-24 17:44:09.225 1390-1390/? E/MainActivity: onCreate: select * from user where filter = 1 and userName='lucy'
03-24 17:44:09.226 1390-1390/? E/MainActivity: onCreate: select * from user where filter = 1 and email='zhangqilu@qq.com'