深入理解Java中的注解以及案例演示

1 注解

1.1 什么是注解?

  Java注解是附加在代码中的一些元数据形式,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。元注解包含在 java.lang.annotation 包中。
在这里插入图片描述

1.2 注解的用处

  1. 生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等。
  2. 跟踪代码依赖性,实现替代配置文件功能。比如spring、mybatis等各种框架 依赖注入,未来java
    开发,将大量注解配置,具有很大用处;
  3. 在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

1.3 注解的原理

  注解本质是一个继承了Annotation 的特殊接口,其具体实现类是Java 运行时生成的动态代理类。而我们通过反射获取注解时,返回的是Java 运行时生成的动态代理对象$Proxy1。通过代理对象调用自定义注解(接口)的方法,会最终调用AnnotationInvocationHandler 的invoke 方法。该方法会从memberValues 这个Map 中索引出对应的值。而memberValues 的来源是Java 常量池。
  注解相关内部处理类,位于sun.reflect.annotation包。
在这里插入图片描述

1.4 基本语法

1.4.1 注解类型的声明部分

  注解在Java中,与类、接口、枚举类似,因此其声明语法基本一致,只是所使用的关键字有所不同@interface。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。

1.4.2 注解类型的实现部分

  根据我们在自定义类的经验,在类的实现部分无非就是书写构造、属性或方法。但是,在自定义注解中,其实现部分只能定义一个东西:注解类型元素(annotation type element)。
定义注解类型元素时需要注意如下几点:

  1. 访问修饰符必须为public,不写默认为public;
  2. 该元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型(体现了注解的嵌套效果)以及上述类型的一维数组;
  3. 该元素的名称一般定义为名词,如果注解中只有一个元素,请把名字起为value(后面使用会带来便利操作);
  4. ()不是定义方法参数的地方,也不能在括号中定义任何参数,仅仅只是一个特殊的语法;
  5. default代表默认值,值必须和第2点定义的类型一致;
  6. 如果没有默认值,代表后续使用注解时必须给该类型元素赋值。
    可以看出,注解类型元素的语法非常奇怪,即又有属性的特征(可以赋值),又有方法的特征(打上了一对括号)。但是这么设计是有道理的,我们在后面的章节中可以看到:注解在定义好了以后,使用的时候操作元素类型像在操作属性,解析的时候操作元素类型像在操作方法。

1.5 常用的元注解

  一个最最基本的注解定义就只包括了上面的两部分内容:1、注解的名字;2、注解包含的类型元素。但是,我们在使用JDK自带注解的时候发现,有些注解只能写在方法上面(比如@Override);有些却可以写在类的上面(比如@Deprecated)。当然除此以外还有很多细节性的定义,那么这些定义该如何做呢?接下来就该元注解出场了!

元注解:专门修饰注解的注解。它们都是为了更好的设计自定义注解的细节而专门设计的。元注解位于 java.lang.annotation包中。

1.5.1 @Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

  @Target注解,是专门用来限定某个自定义注解能够被应用在哪些Java元素上面的。如果在@Target注解中多次出现相同的枚举常量,那么这是一个编译时错误。
  Target注解使用一个枚举类型定义如下可选的参数:

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

    /** 属性的声明 */
    FIELD,

    /** 方法的声明 */
    METHOD,

    /** 方法形式参数声明 */
    PARAMETER,

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

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

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

    /** 包的声明 */
    PACKAGE
}

1.5.2 @Retention

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}

  @Retention注解,翻译为持久力、保持力。即用来修饰自定义注解的生命力。
  注解的生命周期有三个阶段:

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

  同样使用了RetentionPolicy枚举类型定义了三个阶段:

public enum RetentionPolicy {
    /**
     * 注解将被编译器忽略掉
     */
    SOURCE,

    /**
     * 注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为
     */
    CLASS,

    /**
     * 注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

1.5.3 @Documented

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

  @Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。

1.5.4 @Inherited

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

  @Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。

1.5.5 @Repeatable

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}

@Repeatable表示注解是否是可重复。@Repeatable 是 Java 1.8 才加进来的。其参数是注解类型的Class,表示该注解可以重复使用。

1.5.6 @Native

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface Native {
}

  表明一个字段引用的值可能来自于本地代码。是 Java 1.8 才加进来的,目前使用比较少。

1.6 自定义注解

1.6.1 基本类

首先,定义一个注解、和一个供注解修饰的简单Java类:

/**
 * Target:表示ManAnnotation被限定能使用在类、接口或方法上面
 * Documented:表示ManAnnotation可用于生成到JavaDoc文档中
 *
 * @author lx
 */
@Documented
@Target(value = {ElementType.TYPE, ElementType.METHOD})
public @interface ManAnnotation {
    String name();

    int age() default 20;

    String[] tel();
}

public class Student {

    @ManAnnotation(name = "stu", age = 15, tel = {"111111", "66666", "88888"})
    public void learn(int times) {
        for (int i = 0; i < times; i++) {
            System.out.println("Start learning");
        }
    }
}

1.6.2 特殊语法

特殊语法一:
  如果注解本身没有注解类型元素,那么在使用注解的时候可以省略(),直接写为:@注解名,它和标准语法@注解名()等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface FirstAnnotation {
}
//等效于@FirstAnnotation()
@FirstAnnotation
public class JavaBean{
	//省略实现部分
}

特殊语法二:
  如果注解本本身只有一个注解类型元素,而且命名为value,那么在使用注解的时候可以直接使用:@注解名(注解值),其等效于:@注解名(value = 注解值)

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
	String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
	//省略实现部分
}

特殊用法三:
  如果注解中的某个注解类型元素是一个数组类型,在使用时又出现只需要填入一个值的情况,那么在使用注解时可以直接写为:@注解名(类型名 = 类型值),它和标准写法:@注解名(类型名 = {类型值})等效!

@Retention(RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE})
@Documented
public @interface ThirdAnnotation {
	String[] name();
}
//等效于@ ThirdAnnotation(name = {"this is third annotation"})
@ ThirdAnnotation(name = "this is third annotation")
public class JavaBean{
	//省略实现部分
}

特殊用法四:
  如果一个注解的@Target是定义为Element.PACKAGE,那么这个注解是配置在package-info.java中的,而不能直接在某个类的package代码上面配置。
其他:
  注解保持力的三个阶段:Java源文件阶段;编译到class文件阶段;运行期阶段。只有当注解的保持力处于运行阶段,即使用@Retention(RetentionPolicy.RUNTIME)修饰注解时,才能在JVM运行时,检测到注解,并进行一系列特殊操作。

1.6.3 反射操作获取注解

注解以及注解的类容可以通过反射获取。如果你不知道反射,可以看这篇博客:深入理解Java中的反射以及案例演示

        /*获取Student的Class对象*/
        Class<Student> stuClass = Student.class;

        /*获取learn方法对象*/
        Method learnMethod = stuClass.getDeclaredMethod("learn", int.class);
        /*如果方法上有该注解类型,则获取注解*/
        if (learnMethod.isAnnotationPresent(ManAnnotation.class)) {
            System.out.println("Student类上配置了ManAnnotation注解!");
            //获取该元素上指定类型的注解
            ManAnnotation cherryAnnotation = learnMethod.getAnnotation(ManAnnotation.class);
            System.out.println("name: " + cherryAnnotation.name() + ", age: " + cherryAnnotation.age()
                    + ", tel: " + cherryAnnotation.tel()[0]);
        } else {
            System.out.println("Student类上没有配置ManAnnotation注解!");
        }

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

发布了58 篇原创文章 · 获赞 105 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_43767015/article/details/105144393