天天看点

Java_注解_01_注解(Annotation)详解

一、注解的概念

Annotation(注解)是插入代码中的元数据(元数据从metadata一词译来,就是“描述数据的数据”的意思),在JDK5.0及以后版本引入。它可以在编译期使用预编译工具进行处理, 也可以在运行期使用 Java 反射机制进行处理,用于创建文档,跟踪代码中的依赖性,甚至执行基本编译时检查。因为本质上,Annotion是一种特殊的接口,程序可以通过反射来获取指定程序元素的Annotion对象,然后通过Annotion对象来获取注解里面的元数据。

二、注解的本质

2.1 通过示例看清本质

注解本质上是一种继承自接口`java.lang.annotation.Annotation`的特殊接口。

一个自定义注解的示例:

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PersonAnno {

    String name() default "";
    int age() default 0;
}      

View Code

本质上注解会被编译为继承了(Annotation接口)的接口,反编译上面的PersonAnno.class可以看到代码如下: 

Java_注解_01_注解(Annotation)详解

2.2 注解源码

java.lang.annotation.Annotation类源码如下:

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
/**
 * The common interface extended by all annotation types.  Note that an
 * interface that manually extends this one does <i>not</i> define
 * an annotation type.  Also note that this interface does not itself
 * define an annotation type.
 *
 * @author  Josh Bloch
 * @since   1.5
 */
/**
 * 首先声明英语不是很好,大致意思是:这是一个基础接口,所有的注解类型都继承与它。但是需要注意的是
 * (1)不需要手动指明一个注解类型是继承与它的(意思是自动继承)
 * (2)它本身不是注解类型
 */
public interface Annotation {
    /**
     * 这三个方法就不用多说了吧!
     */
    boolean equals(Object obj);
   
    int hashCode();

    String toString();

    /**
     * Returns the annotation type of this annotation.
     */
    /**
     * 返回注解的class
     */
    Class<? extends Annotation> annotationType();
}      

2.3 注解本质的总结

(1)注解实质上会被编译器编译为接口,并且继承java.lang.annotation.Annotation接口。

(2)注解的成员变量会被编译器编译为同名的抽象方法。

(3)根据Java的class文件规范,class文件中会在程序元素的属性位置记录注解信息。例 如,RuntimeVisibleAnnotations属性位置,记录修饰该类的注解有哪些;flags属性位置,记录该类是不是注解;在方法的 AnnotationDefault属性位置,记录注解的成员变量默认值是多少。 

三、注解的作用

Annotation的作用大致可分为三类:

(1)编写文档:通过代码里标识的元数据生成文档;

(2)代码分析:通过代码里标识的元数据对代码进行分析;

(3)编译检查:通过代码里标识的元数据让编译器能实现基本的编译检查;

综上所述可知,Annotation主要用于提升软件的质量和提高软件的生产效率。

四、注解的分类

4.1 注解分类

4.1.1 根据成员个数分类

(1)标记注解:没有定义成员的Annotation类型,自身代表某类信息,如:@Override

(2)单成员注解:只定义了一个成员,比如@SuppressWarnings 定义了一个成员String[] value,使用value={…}大括号来声明数组值,一般也可以省略“value=”

(3)多成员注解:定义了多个成员,使用时以name=value对分别提供数据

4.1.2 根据注解使用的功能和用途分类

(1)Java内置注解:Java自带的注解类型

                @Override:用于修饰此方法覆盖了父类的方法;

                @Deprecated:用于修饰已经过时的方法;

                @SuppressWarnnings:用于通知java编译器禁止特定的编译警告;

(2)元注解:注解的注解,负责注解其他注解

               @Target:用于描述注解可以修饰的类型

               @Retention:用于声明注解的生命周期,即注解在什么范围内有效。

               @Documented:是一个标记注解,表明含有该注解类型的元素(带有注释的)会通过javadoc或类似工具进行文档化。

               @Inherited:是一个标记注解,表示该注解类型能被自动继承。

               @Repeatable :规定注解是否可以重复,重复型的注解还需要指明注解容器,用来存储可重复性注解,同样也是 Java 8 之后才支持

(3)自定义注解:用户根据自己的需求自定义的注解类型

       使用@interface自定义注解,自动继承了java.lang.annotation.Annotation接口

五、Java内置注解

5.1 @Override(覆写) ——限定重写父类方法

(1)源码:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}      

(2)分析:

@Override 是一个标记注解,标注于方法上,仅保留在java源文件中。

(3)用途:

用于告知编译器,我们需要覆写超类的当前方法。如果某个方法带有该注解但并没有覆写超类相应的方法,则编译器会生成一条错误信息。

5.2 @Deprecated(不赞成使用)——用于标记已过时方法

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}      

@Deprecated 是一个标记注解,可标注于除注解类型声明之外的所有元素,保留时长为运行时VM。

用于告知编译器,某一程序元素(比如方法,成员变量)不建议使用时,应该使用这个注解。Java在javadoc中推荐使用该注解,一般应该提供为什么该方法不推荐使用以及相应替代方法。

5.3 @SuppressWarnings(抑制警告)——抑制编译器警告

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}      

 @SuppressWarnings有一个类型为String[]的成员,不是标记注解,这个成员的值为被禁止的警告名,可标注于除注解类型声明和包名之外的所有元素,仅保留在java源文件中。

用于告知编译器忽略特定的警告信息。

(4)使用示例:

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
public class SuppressWarningTest {
    @SuppressWarnings("unchecked")
    public void addItems2(String item){
        @SuppressWarnings("unused")
        List list = new ArrayList();
        List items = new ArrayList();
        items.add(item);
    }

    @SuppressWarnings({"unchecked","unused"})
    public void addItems1(String item){
        List list = new ArrayList();
        list.add(item);
    }

    @SuppressWarnings("all")
    public void addItems(String item){
        List list = new ArrayList();
        list.add(item);
    }
}      

(5)常见参数值

该注解有方法value(),可支持多个字符串参数,例如:

@SupressWarning(value={"uncheck","deprecation"})      

前面讲的@Override,@Deprecated都是无需参数的,而压制警告是需要带有参数的,可用参数如下:

参数 含义
deprecation 使用了过时的类或方法时的警告
unchecked 执行了未检查的转换时的警告
fallthrough 当Switch程序块进入进入下一个case而没有Break时的警告
path 在类路径、源文件路径等有不存在路径时的警告
serial 当可序列化的类缺少serialVersionUID定义时的警告
finally 任意finally子句不能正常完成时的警告
all 以上所有情况的警告
更多关键字

六、元注解

元注解的作用就是负责注解其他注解。

6.1 @Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    ElementType[] value();
}      

(2)作用:

用于描述注解可以修饰的程序元素。当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。

如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。程序元素(ElementType)是枚举类型,共定义8种程序元素,如下表:

ElementType
ANNOTATION_TYPE 注解类型声明
CONSTRUCTOR 构造方法声明
FIELD 字段声明(包括枚举常量)
LOCAL_VARIABLE 局部变量声明
METHOD 方法声明
PACKAGE 包声明
PARAMETER 参数声明
TYPE 类、接口(包括注解类型)或枚举声明

例如,上面源码@Target的定义中有一行  @Target(ElementType.ANNOTATION_TYPE) ,意思是指当前注解的元素类型是注解类型。

6.2 @Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    RetentionPolicy value();
}      

描述该注解的生命周期,表示在什么编译级别上保存该注解的信息。

当注解类型声明中没有@Retention元注解,则默认保留策略为RetentionPolicy.CLASS。

保留策略(RetentionPolicy)是枚举类型,共定义3种保留策略,如下表:

RetentionPolicy
SOURCE 仅存在Java源文件,经过编译器后便丢弃相应的注解
CLASS 存在Java源文件,以及经编译器后生成的Class字节码文件,但在运行时VM不再保留注释
RUNTIME 存在源文件、编译生成的Class字节码文件,以及保留在运行时VM中,可通过反射性地读取注解

例如,上面源码@Retention的定义中有一行 @Retention(RetentionPolicy.RUNTIME),意思是指当前注解的保留策略为RUNTIME,即存在Java源文件,也存在经过编译器编译后的生成的Class字节码文件,同时在运行时虚拟机(VM)中也保留该注解,可通过反射机制获取当前注解内容。

6.3 @Documented

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}      

是一个标记注解,表示拥有该注解的元素可通过javadoc此类的工具进行文档化,即在生成javadoc文档的时候将该Annotation也写入到文档中。

例如,上面源码@Retention的定义中有一行 @Documented,意思是指当前注解的元素会被javadoc工具进行文档化,那么在查看Java API文档时可查看当该注解元素。

6.4 @Inherited

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}      

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

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

七、自定义注解

7.1 自定义注解的规则

自定义注解示例:

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
/**
 *自定义注解MyAnnotation
 */
@Target(ElementType.TYPE) //目标对象是类型
@Retention(RetentionPolicy.RUNTIME) //保存至运行时
@Documented //生成javadoc文档时,该注解内容一起生成文档
@Inherited //该注解被子类继承
public @interface MyAnnotation {
    public String value() default ""; //当只有一个元素时,建议元素名定义为value(),这样使用时赋值可以省略"value="
    String name() default "devin"; //String
    int age() default 18; //int
    boolean isStudent() default true; //boolean
    String[] alias(); //数组
    enum Color {GREEN, BLUE, RED,} //枚举类型
    Color favoriteColor() default Color.GREEN; //枚举值
}      

自定义注解规则:

(1)定义注解:使用@interface来声明一个注解,同时将自动继承java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。

(2)配置注解参数(key):注解的每一个方法实际上是声明了一个配置参数。注解方法不带参数,方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。

(3)注解参数的默认值(value):可以通过default来声明参数的默认值。

(4)注解参数的可支持数据类型:基本类型、String、Enums、Annotation以及前面这些类型的数组类型。

(5)注解参数的访问权限:只能用public或默认(default)这两个访问权修饰。

(6)如果只有一个参数成员,建议参数名称设为value()。

(7)注解元素必须有确定的值,要么在定义注解的默认值中指定,要么在使用注解时指定,非基本类型的注解元素的值不可为null。因此, 使用空字符串或负数作为默认值是一种常用的做法。

7.2 使用自定义注解

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
@MyAnnotation(
        value = "info",
        name = "myname",
        age = 99,
        isStudent = false,
        alias = {"name1", "name2"},
        favoriteColor = MyAnnotation.Color.RED
)
public class MyClass {
    //使用MyAnnotation注解,该类生成的javadoc文档包含注解信息如下:
    /*
    @MyAnnotation(value = "info", name = "myname", age = 99, isStudent = false, alias = {"name1","name2"}, favoriteColor = Color.RED)
    public class MyClass
    extends Object
     */
}


public class MySubClass extends MyClass{
    //子类MySubClass继承了父类MyClass的注解
}
七、解      

八、 解析注解信息

8.1 AnnotatedElement 接口

Java使用Annotation接口来代表程序元素前面的注解,该接口是所有Annotation类型的父接口。

通过反射技术来解析自定义注解,关于反射类位于包java.lang.reflect,其中有一个接口 AnnotatedElement,该接口代表程序中可以接受注解的程序元素。

AnnotatedElement接口是所有程序元素(Field、Method、Package、Class和Constructor)的父接口,所以程序通过反射获取了某个类的AnnotatedElement对象之后,程序就可以调用该对象的如下七个方法来访问Annotation信息:

返回值 方法 解释
T getAnnotation(Class annotationClass) 当存在该元素的指定类型注解,则返回相应注释,否则返回null
Annotation[] getAnnotations() 返回此元素上存在的所有注解
getDeclaredAnnotation(Class) 返回该程序元素上存在的、指定类型的注解,如果该类型注解不存在,则返回null;与此接口中的其他方法不同,该方法将忽略继承的注解;
getDeclaredAnnotations() 返回直接存在于此元素上的所有注释。与此接口中的其他方法不同,该方法将忽略继承的注解;
getAnnotationsByType(Class) 返回直接存在于此元素上指定注解类型的所有注解;
boolean  isAnnotationPresent (Class<?extends Annotation> annotationClass) 判断该程序元素上是否包含指定类型的注解,存在则返回true,否则返回false;

8.1.2 isAnnotationPresent(Class<?extends Annotation>)源码

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
public boolean isAnnotationPresent(
        Class<? extends Annotation> annotationClass) {
        if (annotationClass == null)
            throw new NullPointerException();

        return getAnnotation(annotationClass) != null;
    }      

其实是调用了 getAnnotation(Class )

8.1.3 getAnnotation(Class )源码

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
public <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
        if (annotationClass == null)
            throw new NullPointerException();

        initAnnotationsIfNecessary();//初始化
        return (A) annotations.get(annotationClass);
    }      

initAnnotationsIfNecessary()  源码

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
//看了下这个方法。基本上就是扫描后放入到annotations中,完成初始话!
 private synchronized void initAnnotationsIfNecessary() {
        clearCachesOnClassRedefinition();//看了下这个方法,好像是清理缓存用的。
        if (annotations != null)
            return;
        declaredAnnotations = AnnotationParser.parseAnnotations(
            getRawAnnotations(), getConstantPool(), this);//这个方法没法找到啊
        Class<?> superClass = getSuperclass();
        if (superClass == null) {
            annotations = declaredAnnotations;
        } else {
            annotations = new HashMap<Class, Annotation>();
            superClass.initAnnotationsIfNecessary();
            for (Map.Entry<Class, Annotation> e : superClass.annotations.entrySet()) {
                Class annotationClass = e.getKey();
                if (AnnotationType.getInstance(annotationClass).isInherited())
                    annotations.put(annotationClass, e.getValue());
            }
            annotations.putAll(declaredAnnotations);
        }
    }      

8.2 解析注解示例

(1)FruitName注解

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
package com.ray.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
/**
 * 水果名称注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitName {
    String value() default " ";
}      

(2)FruitColor注解

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
package com.ray.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
/**
 * 水果颜色注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitColor {
    /**
     * 颜色枚举
     */
    public enum Color{BLUE, RED, GREEN};

    /**
     * 颜色属性
     * @return
     */
    Color fruitColor() default Color.GREEN;
}      

(3)FruitProvider注解

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
package com.ray.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.ElementType;
/**
 * 水果供应商注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FruitProvider {
    /**
     * 供应商编号
     * @return
     */
    public int id() default -1;

    /**
     * 供应商名称
     * @return
     */
    public String name() default " ";

    /**
     * 供应商地址
     * @return
     */
    public String address() default " ";
}      

(4)实体类——Apple类

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
package com.ray.annotation;

/***********注解使用***************/
public class Apple {
    @FruitName("Apple")
    private String appleName;
    
    @FruitColor(fruitColor = FruitColor.Color.RED)
    private String appleColor;
    
    @FruitProvider(id = 1, name = "陕西红富士集团", address = "陕西红富士大厦")
    private String appleProvider;

    public String getAppleProvider() {
        return appleProvider;
    }

    public void setAppleProvider(String appleProvider) {
        this.appleProvider = appleProvider;
    }

    public String getAppleName() {
        return appleName;
    }

    public void setAppleName(String appleName) {
        this.appleName = appleName;
    }

    public String getAppleColor() {
        return appleColor;
    }

    public void setAppleColor(String appleColor) {
        this.appleColor = appleColor;
    }

    public void displayName(){
        System.out.println(getAppleName());
    }
}      

(5)注解解析类——AnnotationParser类

Java_注解_01_注解(Annotation)详解
Java_注解_01_注解(Annotation)详解
package com.ray.annotation;

import java.lang.reflect.Field;

public class AnnotationParser {
    public static void main(String[] args) throws SecurityException, ClassNotFoundException {
        //1.获取苹果类的属性
        String clazz = "com.ray.annotation.Apple";
        //Field[] fields = AnnotationParser.class.getClassLoader().loadClass(clazz).getDeclaredFields();
        Field[] fields = Class.forName(clazz).getDeclaredFields();
        
        //2.解析注解信息
        for (Field field : fields) {
            //System.out.println(field.getName().toString());
            //2.1当field上标注了FruitName注解时
            if (field.isAnnotationPresent(FruitName.class)){
                FruitName fruitName = field.getAnnotation(FruitName.class);
                System.out.println("水果的名称:" + fruitName.value());

                //2.2当field上标注了FruitColor注解时
            }else if (field.isAnnotationPresent(FruitColor.class)){
                FruitColor fruitColor = field.getAnnotation(FruitColor.class);
                System.out.println("水果的颜色:"+fruitColor.fruitColor());
            }else if (field.isAnnotationPresent(FruitProvider.class)){
                FruitProvider fruitProvider = field.getAnnotation(FruitProvider.class);
                System.out.println("水果供应商编号:" + fruitProvider.id() + " 名称:" + fruitProvider.name() + " 地址:" + fruitProvider.address());
            }
        }
    }

}      

参考文章:

1.

java注解解析

2.

Java 注解深入理解

4.

Java注解(Annotation)

5.

Java annotation源码解读

6.

聊聊 Java 注解(上)

7.

Java Annotation认知(包括框架图、详细介绍、示例说明)