自定义注解

An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

注解是一种能被添加到java代码中的元数据,类、方法、变量、参数和包都可以用注解来修饰。注解对于它所修饰的代码并没有直接的影响。

Annotations have a number of uses, among them:Information for the complier - Annotations can be used by the compiler to detect errors or suppress warnings.Compiler-time and deployment-time processing - Software tools can process annotation information to generate code, XML files, and so forth.Runtime processing - Some annotations are available to be examined at runtime.

注解有许多用法,其中有:

  1. 为编译器提供信息 - 注解能被编译器检测到错误或抑制警告。
  2. 编译时和部署时的处理 - 软件工具能处理注解信息从而生成代码,XML文件等等。
  3. 运行时的处理 - 有些注解在运行时能被检测到。

Java 自定义注解

简单说注解就是一种标签类型

image

1 基本语法

1.1 注解类型的声明部分:

在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口

如果采用编译原理往往用文法描述,这里直接使用Java习惯。

public @interface AnnotationName {

}

1.2 注解类型的实现部分:

在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。咱们来看看其语法:

import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

/**
 * 我们做3C的时候会读取各种源端,以此为例
 * 自定义注解,其实本质就是一种类型和类,接口,枚举一样
 * <p>
 * 自定义主机的定义包含两部分:
 * 类型定义,和元素定义
 * 元素定义说明:
 * 1、必须是public,可以省略
 * 2、元素类型是部分数据类型支持:基本类型、String、Class<? extends MyClass>、enum、注解类型、前面所有类型的数组
 * 2、元素名称名称后面还要跟(),可以看做获取值的方法
 * 3、default定义默认值,如果没有默认值,使用注解必须赋值
 * 4、数组的赋值{},可以忽略value=
 */
@Target(value={ElementType.TYPE,ElementType.METHOD})
public @interface Reader {
    public String type() default "ES";

    int order() default 1;
}

注解里面定义的是:注解类型元素!可以通过default设置默认值

注解类型元素,即有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。

注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法

1.3 注解的作用

  • 注解是一种元数据形式(元数据: 用数据来描述数据. java中是以注解来表示的)。即注解是属于java的一种数据类型,和类、接口、数组、枚举类似(它们处于同一个级别)。

  • 注解用来修饰,类、方法、变量、参数、包。

  • 注解不会对所修饰的代码产生直接的影响。

2、常用元注解

元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的

2.1、@Target

public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    TYPE,

    /** Field declaration (includes enum constants) */
    FIELD,

    /** Method declaration */
    METHOD,

    /** Formal parameter declaration */
    PARAMETER,

    /** Constructor declaration */
    CONSTRUCTOR,

    /** Local variable declaration */
    LOCAL_VARIABLE,

    /** Annotation type declaration */
    ANNOTATION_TYPE,

    /** Package declaration */
    PACKAGE,

    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    TYPE_PARAMETER,

    /**
     * Use of a type
     *
     * @since 1.8
     */
    TYPE_USE
}

表明该注解可以应用的java元素类型。

Target类型 描述
ElementType.TYPE 应用于类、接口(包括注解类型)、枚举
ElementType.FIELD 应用于属性(包括枚举中的常量)
ElementType.METHOD 应用于方法
ElementType.PARAMETER 应用于方法的形参
ElementType.CONSTRUCTOR 应用于构造函数(特殊方法)
ElementType.LOCAL_VARIABLE 应用于局部变量
ElementType.ANNOTATION_TYPE 应用于注解类型
ElementType.PACKAGE 应用于包
ElementType.TYPE_PARAMETER 1.8版本新增,应用于类型变量)
ElementType.TYPE_USE 1.8版本新增,应用于任何使用类型的语句中(例如声明语句、泛型和强制转换语句中的类型)

如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。

2.2、@Retention

表明该注解的生命周期。

public enum RetentionPolicy {
    /**
     * Annotations are to be discarded by the compiler.
     */
    SOURCE,

    /**
     * Annotations are to be recorded in the class file by the compiler
     * but need not be retained by the VM at run time.  This is the default
     * behavior.
     */
    CLASS,

    /**
     * Annotations are to be recorded in the class file by the compiler and
     * retained by the VM at run time, so they may be read reflectively.
     *
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}
生命周期类型 描述
RetentionPolicy.SOURCE 编译时被丢弃,不包含在类文件中,相当于注释
RetentionPolicy.CLASS JVM加载时被丢弃,包含在类文件中,默认值
RetentionPolicy.RUNTIME 由JVM 加载,包含在类文件中,在运行时可以被获取到

(1) 如果一个注解被定义为RetentionPolicy.SOURCE,则它将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果,只能被阅读Java文件的人看到;
(2) 如果一个注解被定义为RetentionPolicy.CLASS,则它将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到;
(3) 如果一个注解被定义为RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME;
(4) 在默认的情况下,自定义注解是使用的RetentionPolicy.CLASS。

2.3、@Documented

表明该注解标记的元素可以被Javadoc 或类似的工具文档化。

2.4、@Inherited

表明使用了@Inherited注解的注解,所标记的类的子类也会拥有这个注解

@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

3、自定义注解的运行时解析

这一章是使用注解的核心,读完此章即可明白,如何在程序运行时检测到注解,并进行一系列特殊操作

3.1 回顾注解的保持力

首先回顾一下,之前自定义的注解@CherryAnnotation,并把它配置在了类Student上,代码如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.METHOD})
@Documented
public @interface CherryAnnotation {
    String name();
    int age() default 18;
    int[] score();
}

package pojos;
public class Student {
    @CherryAnnotation(name = "cherry-peng",age = 23,score = {99,66,77})
    public void study(int times){
        for(int i = 0; i < times; i++){
            System.out.println("Good Good Study, Day Day Up!");
        }
    }
}

注解保持力的三个阶段:

  1. Java源文件阶段;
  2. 编译到class文件阶段;
  3. 运行期阶段。

只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。

3.2 反射操作获取注解

因此,明确我们的目标:在运行期探究和使用编译期的内容(编译期配置的注解),要用到Java中的灵魂技术——反射!

public static void main(String[] args){
        try {
            //获取Student的Class对象
            Class stuClass = Class.forName("pojos.Student");
            //说明一下,这里形参不能写成Integer.class,应写为int.class
            Method stuMethod = stuClass.getMethod("study",int.class);
            if(stuMethod.isAnnotationPresent(CherryAnnotation.class)){
                System.out.println("Student类上配置了CherryAnnotation注解!");
                //获取该元素上指定类型的注解
                CherryAnnotation cherryAnnotation = stuMethod.getAnnotation(CherryAnnotation.class);
                System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age()

                    + ", score: " + cherryAnnotation.score()[0]);
            }else{
                System.out.println("Student类上没有配置CherryAnnotation注解!");
            }

        } catch (ClassNotFoundException e) {
            e.printStackTrace();

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

解释一下:

(1) 如果我们要获得的注解是配置在方法上的,那么我们要从Method对象上获取;如果是配置在属性上,就需要从该属性对应的Field对象上去获取,如果是配置在类型上,需要从Class对象上去获取。总之在谁身上,就从谁身上去获取!
(2) isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解;
(3) getAnnotation(Class annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
(4) 反射对象上还有一个方法getAnnotations(),该方法可以获得该对象身上配置的所有的注解。它会返回给我们一个注解数组,需要注意的是该数组的类型是Annotation类型,这个Annotation是一个来自于java.lang.annotation包的接口。

posted @ 2022-11-19 10:53  红尘过客2022  阅读(102)  评论(0编辑  收藏  举报