Java:annotation注解的简单理解和总结

Posted JMW1407

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java:annotation注解的简单理解和总结相关的知识,希望对你有一定的参考价值。

注解Annotation

秒懂,Java 注解 (Annotation)你可以这样学

1、Annotation的概述

1.1、定义

注解(Annotation),也叫元数据,是一种代码级别的说明。

它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

其实Annotation就是代码里的特殊标记,它们可以在编译,类加载,运行时被读取,并执行相应的处理,以便于其他工具补充信息或者进行部署。

注意:Annotation能被用来为程序元素(类、方法、成员变量等)设置元素据。Annotaion不影响程序代码的执行,无论增加、删除Annotation,代码都始终如一地执行。如果希望让程序中的Annotation起一定的作用,只有通过解析工具或编译工具对Annotation中的信息进行解析和处理。

1.2、Annotation作用分类

一般来说,元数据可以用于创建文档(根据程序元素上的注释创建文档),跟踪代码中的依赖性(可声明方法是重载,依赖父类的方法),执行编译时检查(可声明是否编译期检测),代码分析。
如下:

  • 1、编写文档:通过注解生成API文档(@Documented)
  • 2、编译检查:通过注解让编译器实现基本的编译检查(@Override)
  • 3、代码分析:通过注解对代码进行分析【反射等】(重点)

1.3、Annotation 架构


从中,我们可以看出:

  • (1) 1 个 Annotation 和 1 个 RetentionPolicy关联。可以理解为:每1个Annotation对象,都会有唯一的RetentionPolicy属性。

  • (2) 1 个 Annotation 和 1~n 个 ElementType 关联。可以理解为:对于每 1 个 Annotation 对象,可以有若干个 ElementType 属性。

  • (3) Annotation 有许多实现类,包括:Deprecated, Documented, Inherited, Override 等等。 Annotation 的每一个实现类,都 “和 1 个 RetentionPolicy 关联” 并且 " 和 1~n 个 ElementType 关联"。

下面,我先介绍框架图的左半边(如下图),即 Annotation, RetentionPolicy, ElementType;然后在就 Annotation 的实现类进行举例说明。

Annotation 组成部分

java Annotation 的组成中,有 3 个非常重要的主干类。它们分别是:

Annotation.java

package java.lang.annotation;
public interface Annotation {

    boolean equals(Object obj);

    int hashCode();

    String toString();

    Class<? extends Annotation> annotationType();
}

ElementType.java

package java.lang.annotation;

public enum ElementType {
    TYPE,               /* 类、接口(包括注释类型)或枚举声明  */

    FIELD,              /* 字段声明(包括枚举常量)  */

    METHOD,             /* 方法声明  */

    PARAMETER,          /* 参数声明  */

    CONSTRUCTOR,        /* 构造方法声明  */

    LOCAL_VARIABLE,     /* 局部变量声明  */

    ANNOTATION_TYPE,    /* 注释类型声明  */

    PACKAGE             /* 包声明  */
}

RetentionPolicy.java

package java.lang.annotation;
public enum RetentionPolicy {
    SOURCE,            /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了  */

    CLASS,             /* 编译器将Annotation存储于类对应的.class文件中。默认行为  */

    RUNTIME            /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
}

说明:

(1) Annotation 就是个接口。
“每 1 个 Annotation” 都与 “1 个 RetentionPolicy” 关联,并且与 “1~n 个 ElementType” 关联。可以通俗的理解为:每 1 个 Annotation 对象,都会有唯一的 RetentionPolicy 属性;至于 ElementType 属性,则有 1~n 个。

(2) ElementType 是 Enum 枚举类型,它用来指定 Annotation 的类型。
“每 1 个 Annotation” 都与 “1~n 个 ElementType” 关联。当 Annotation 与某个 ElementType 关联时,就意味着:Annotation有了某种用途。例如,若一个 Annotation 对象是 METHOD 类型,则该 Annotation 只能用来修饰方法。

(3) RetentionPolicy 是 Enum 枚举类型,它用来指定 Annotation 的策略。通俗点说,就是不同 RetentionPolicy 类型的 Annotation 的作用域不同。

“每 1 个 Annotation” 都与 “1 个 RetentionPolicy” 关联。

  • a) 若 Annotation 的类型为 SOURCE,则意味着:Annotation 仅存在于编译器处理期间,编译器处理完之后,该Annotation 就没用了。 例如," @Override" 标志就是一个Annotation。当它修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,"@Override"就没有任何作用了。
  • b) 若 Annotation 的类型为 CLASS,则意味着:编译器将 Annotation 存储于类对应的 .class 文件中,它是Annotation 的默认行为。
  • c) 若 Annotation 的类型为 RUNTIME,则意味着:编译器将 Annotation 存储于 class文件中,并且可由JVM读入。

这时,只需要记住"每 1 个 Annotation" 都与 “1 个 RetentionPolicy” 关联,并且与 “1~n 个 ElementType” 关联。

2、Annotation的语法形式

public @interface 注解名称 {}

我们可以定义一个简单的注解,如下:

package com.hanyxx.annotaion;
 
/**
 * @author xxx
 * @description: 自定义注解
 */
public @interface bocai {
}
如果通过 javac命令将注解类编译,然后再通过 javap 命令进行反编译,就会发现:
public interface com.hanyxx.annotaion.bocai extends java.lang.annotation.Annotation {}

注解的本质就是:接口

3、Annotation的分类

java内置了6个基本注解和4个元注解

3.1、基本注解

@Override :检测被该注解标注的方法是继承自父类(接口)的

@Deprecated:该注解标注的内容,表示已过时

@SuppressWarnings:压制警告

其中jdk1.7之后引入了另外两个基本注解:

@SafeVarargs : Java7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告

@FunctionalInterface : Java8 开始支持,标识一个匿名函数或函数式接口。

@Repeatable: Java8 开始支持,标识某注解可以在同一个声明上使用多次

其中@Override、@Deprecated、@SuppressWarnings、@SafeVarargs
和@FunctionalInterface在java.lang包下

而@Repeatable在java.lang.annotation包下

/**
 * @Date 2021/11/13 14:04
 * @Version 10.21
 * @Author XXX
 */
public class AnnotationTest {
    public static void main(String[] args) {

        @SuppressWarnings("unused")
        int a = 10;
    }

    @Deprecated
    public void myPrint(){
        System.out.println("@Deprecated修饰的方法表示已过时");
    }

    @Override
    public String toString(){
        return "重写的toString()";
    }
}

3.2、元注解

元注解(meta-annotation):定义注解的注解

3.2.1、@Target

@Target:用于描述注解能够作用的范围,可取的值存于ElementType枚举类中。

  • 当注解类型声明中没有@Target元注解,则默认为可适用所有的程序元素。如果存在指定的@Target元注解,则编译器强制实施相应的使用限制。

@Target(ElementType.TYPE)
public @interface Table {
    /**
     * 数据表名称注解,默认值为类名称
     * @return
     */
    public String tableName() default "className";
}
 
@Target(ElementType.FIELD)
public @interface NoDBColumn {
 
}

注解Table 可以用于注解类、接口(包括注解类型) 或enum声明,而注解NoDBColumn仅可用于注解类的成员变量。

3.2.2、@Retention

@Retention:指定所修饰的 Annotation 的生命周期:SOURCE\\CLASS(默认行为)\\RUNTIME只有声明为RUNTIME生命周期的注解,才能通过反射获取。

@Rentention 包含一个 RetentionPolicy 类型的成员变量, 使用@Rentention 时必须为该 value 成员变量指定值:

  • RetentionPolicy.SOURCE:在源文件中有效(即源文件保留),编译器直接丢弃这种策略的注释
  • RetentionPolicy.CLASS:在class文件中有效(即class保留) , 当运行 Java 程序时,
    JVM不会保留注解。 这是默认值
  • RetentionPolicy.RUNTIME:在运行时有效(即运行时保留),当运行 Java程序时, JVM会保留注释。程序可以通过反射获取 该注释。
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField"; 
    public boolean defaultDBValue() default false;
}

Column注解的的RetentionPolicy的属性值是RUTIME,这样注解处理器可以通过反射,获取到该注解的属性值,从而去做一些运行时的逻辑处理

3.2.3、@Documented

@Documented:用于指定被该元 Annotation 修饰的 Annotation 类将被javadoc 工具提取成文档。默认情况下,javadoc是不包括注解的。

  • 定义为Documented的注解必须设置Retention值为RUNTIME。
@author 标明开发该类模块的作者,多个作者之间使用,分割

@version 标明该类模块的版本

@see 参考转向,也就是相关主题

@since 从哪个版本开始增加的

@param 对方法中某参数的说明,如果没有参数就不能写

@return 对方法返回值的说明,如果方法的返回值类型是void就不能写

@exception 对方法可能抛出的异常进行说明 ,如果方法没有用throws显式抛出的异常就不能写

其中@param @return 和 @exception 这三个标记都是只用于方法的。
	@param的格式要求:@param 形参名 形参类型 形参说明
	@return 的格式要求:@return 返回值类型 返回值说明
	@exception的格式要求:@exception 异常类型 异常说明
	@param和@exception可以并列多个

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Column {
    public String name() default "fieldName";
    public String setFuncName() default "setField";
    public String getFuncName() default "getField"; 
    public boolean defaultDBValue() default false;
}

3.2.4、@Inherited:

@Inherited:被它修饰的 Annotation 将具有 继承性。如果某个类使用了被@Inherited 修饰的 Annotation, 则其子类将自动具有该注解。

  • 比如:如果把标有@Inherited注解的自定义的注解标注在类级别上,子类则可以继承父类类级别的注解。

注意:

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

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

/**
 * 
 * @author test
 *
 */
@Inherited
public @interface Greeting {
    public enum FontColor{ BULE,RED,GREEN};
    String name();
    FontColor fontColor() default FontColor.GREEN;
}


4、自定义annotation

4 .1、定义说明

  • 1、定义新的 Annotation 类型使用 @interface 关键字
  • 2、自定义注解自动继承了java.lang.annotation.Annotation 接口
  • 3、Annotation 的成员变量在Annotation定义中以无参数方法的形式来声明。其方法名和返回值定义了该成员的名字和类型。我们称为配置参数。类型只能是八种基本数据类型、String类型、Class 类型 、enum 类型 、Annotation 类型 、以上所有类型的 数组
  • 4、可以在定义 Annotation的成员变量时为其指定初始值, 指定成员变量的初始值可使用 default 关键字
  • 5、如果只有一个参数成员,建议使用 参数名为value
  • 6、如果定义的注解含有配置参数,那么使用时必须指定参数值,除非它有默认值。格式是“参数名 = 参数值”,如果只有一个参数成员,且名称为value,可以省略“value=”
  • 7、没有成员定义的 Annotation 称为 标记; 包含成员变量的 Annotation 称为元数据 Annotation

注意:自定义注解必须配上注解的信息处理流程才有意义。

package com.hanyxx.annotaion;
import java.lang.annotation.*;
/**
 * @author 菠菜饭团
 * @description: 自定义注解
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface BoCai {
    String name() default "XXX";
    ElementType[] value() default ElementType.TYPE ;
    String className();
    String methodName();
}
package com.hanyxx.annotaion;
import java.lang.reflect.Method;
/**
 * @author XXX
 * @description: 通过注解的形式创建任意对象,并且执行任意方法
 */
@BoCai(className="com.hanyxx.annotaion.PlayGame",methodName = "play")
public class AnnotationTest {
    public static void main(String[] args) throws Exception {
        /**
         *  <A extends Annotation> A getAnnotation(Class<A> annotationClass) :如果存在该元素的指定类型的注解,则返回这些注解,否则返回 null。
         *  Annotation[] getAnnotations() : 返回此元素上存在的所有注解。
         */
        //1.获取类字节码对象
        Class<AnnotationTest> annotationClass = AnnotationTest.class;
        //2.获取该类的注解对象(本质是在内存中生成该注解接口的子类实现对象)
        BoCai boCai = annotationClass.getAnnotation(BoCai.class);
        //3.获取传入的类名和方法名
        String className = boCai.className();
        System.out.println("获取类名:"+className);
        System.out.println("===========================");
        String methodName = boCai.methodName();
        System.out.println("获取方法名:"+methodName);
        System.out.println("===========================");
        //4.加载类进内存,并获取方法对象
        Class<?> Cls = Class.forName(className);
        Method method = Cls.getMethod(methodName);
        //5.创建对象实例并调用方法
        Object o = Cls.newInstance();
        method.invoke(o);
    }
}

4.2、实例

MyAnnotation

package com.atguigu.java1;

import java.lang.annotation.*;

import static java.lang.annotation.ElementType.*;

@Inherited
@Repeatable(MyAnnotations.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE,TYPE_PARAMETER,TYPE_USE})
public @interface MyAnnotation {

    String value() default "hello";
}

MyAnnotations

package com.atguigu.java1;

import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.*;

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
public @interface MyAnnotations {

    MyAnnotation[] value();
}

AnnotationTest

package com.atguigu.java1;

import org.junit.Test;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Date;

/**
 * 注解的使用
 *
 * 1. 理解Annotation:
 * ① jdk 5.0 新增的功能
 *
 * ② Annotation 其实就是代码里的特殊标记, 这些标记可以在编译, 类加载, 运行时被读取, 并执行相应的处理。通过使用 Annotation,
 * 程序员可以在不改变原有逻辑的情况下, 在源文件中嵌入一些补充信息。
 *
 * ③在JavaSE中,注解的使用目的比较简单,例如标记过时的功能,忽略警告等。在JavaEE/android
 * 中注解占据了更重要的角色,例如用来配置应用程序的任何切面,代替JavaEE旧版中所遗留的繁冗
 * 代码和XML配置等。
 *
 * 2. Annocation的使用示例
 * 示例一:生成文档相关的注解
 * 示例二:在编译时进行格式检查(JDK内置的三个基本注解)
     @Override: 限定重写父类方法, 该注解只能用于方法
     @Deprecated: 用于表示所修饰的元素(类, 方法等)已过时。通常是因为所修饰的结构危险或存在更好的选择
     @SuppressWarnings: 抑制编译器警告

  * 示例三:跟踪代码依赖性,实现替代配置文件功能
  *
  * 3. 如何自定义注解:参照@SuppressWarnings定义
      * ① 注解声明为:@interface
      * ② 内部定义成员,通常使用value表示
      * ③ 可以指定成员的默认值,使用default定义
      * ④ 如果自定义注解没有成员,表明是一个标识作用。

     如果注解有成员,在使用注解时,需要指明成员的值。
     自定义注解必须配上注解的信息处理流程(使用反射)才有意义。
     自定义注解通过都会指明两个元注解:Retention、Target

     4. jdk 提供的4种元注解
       元注解:对现有的注解进行解释说明的注解
     Retention:指定所修饰的 Annotation 的生命周期:SOURCE\\CLASS(默认行为)\\RUNTIME
            只有声明为RUNTIME生命周期的注解,才能通过反射获取。
     Target:用于指定被修饰的 Annotation 能用于修饰哪些程序元素
     *******出现的频率较低*******
     Documented:表示所修饰的注解在被javadoc解析时,保留下来。
     Inherited:被它修饰的 Annotation 将具有继承性。

     5.通过反射获取注解信息 ---到反射内容时系统讲解

     6. jdk 8 中注解的新特性:可重复注解、类型注解

     6.1 可重复注解:① 在MyAnnotation上声明@Repeatable,成员值为MyAnnotations.class
                    ② MyAnnotation的Target和Retention等元注解与MyAnnotations相同。

     6.2 类型注解:
     ElementType.TYPE_PARAMETER 表示该注解能写在类型变量的声明语句中(如:泛型声明)。
     ElementType.TYPE_USE 表示该注解能写在使用类型的任何语句中。

      *
 */
public class AnnotationTest {

    public static void main(String[] args) {
        Person p = new Student();
        p.walk();

        Date date = new Date(2020, 10, 11);
        System.out.println(date);

        @SuppressWarnings("unused")
        int num = 10;

//        System.out.println(num);

        @SuppressWarnings({ "unused", "rawtypes" })
        ArrayList list = new ArrayList();
    }

    @Test
    public void testGetAnnotation(){
        Class clazz = Student.class;
        Annotation[] annotations = clazz.getAnnotations();
        for(int i = 0;i < annotations.length;i++){
            System.out.println(annotations[i]);
        }
    }
}


//jdk 8之前的写法:
//@MyAnnotations({@MyAnnotation(value="hi"),@MyAnnotation(value="hi")})
@MyAnnotation(value="hi")
@MyAnnotation(value="abc")
class Person{
    private String name;
    private int age;

    public Person() {
    }
    @MyAnnotation
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }
    @MyAnnotation
    public void walk(){
        System.out.println("人走路");
    }
    public void eat(){
        System.out.println("人吃饭");
    }
}

interface Info{
    void show();
}

class Student extends Person implements Info{

    @Override
    public void walk() {
        System.out.println("学生走路");
    }

    public void show() {

    }
}

class Generic<@MyAnnotation T>{

    public void show() throws @MyAnnotation RuntimeException{

        ArrayList<@MyAnnotation String> list = new ArrayList<>();

        int num = (@MyAnnotation int) 10L;
    }

}

参考

1、https://www.cnblogs.com/Tanyboye/p/9138412.html
2、Java 注解(Annotation)

以上是关于Java:annotation注解的简单理解和总结的主要内容,如果未能解决你的问题,请参考以下文章

Java反射学习总结五(Annotation(注解)-基础篇)

[1] 注解(Annotation)-- 深入理解Java:注解(Annotation)基本概念

[2]注解(Annotation)-- 深入理解Java:注解(Annotation)自定义注解入门

深入理解Java:注解(Annotation)基本概念

深入理解Java:注解(Annotation)基本概念

深入理解Java:注解(Annotation)自己定义注解入门