【java基础】注解详解

是什么

从JDK1.5开始,java增加了对元数据(MetaData)的支持,即Annotation 注解。更像是代码里的特殊标记,在不改变原有逻辑的情况下,在源文件中嵌入一些补充的信息,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理。可以用于修饰包、类、构造器、方法、成员变量、参数、局部变量的声明。
注解是一个接口,一个继承了 Annotation 接口的接口,程序可以通过反射来获取指定程序元素的java.lang.annotation.Annotation对象,然后通过java.lang.annotation.Annotation对象来取得注解里面的元数据。

元注解

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface RequestMapping {}

当查看一个注解时,注解本身还存在几个注解,@Target、@Retention、@Documented这三个是【元注解】,【元注解】用于指定某个注解生命周期以及作用目标等信息。

Java中有以下四个【元注解】:

  • @Target 注解的作用目标
  • @Retention 注解的生命周期
  • @Documented 注解是否应当被包含在javaDoc文档中
  • @Inherited 是否允许子类集成该注解

@Target

@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 用于指明被修饰的注解最终可以作用的目标是谁,也就是指明,你的注解到底是用来修饰方法的?修饰类的?还是用来修饰字段属性的。
ElementType 是一个枚举类型,定义如下:

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
}

@Retention

@Retention 用于指明当前注解的生命周期,它的基本定义

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

RetentionPolicy 依然是一个枚举类型,定义如下

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
}

@Documented

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

@Documented 注解修饰的注解,当我们执行 JavaDoc 文档打包时会被保存进 doc 文档,反之将在打包时丢弃。

@Inherited

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

@Inherited 注解修饰的注解是具有可继承性的,也就说我们的注解修饰了一个类,而该类的子类将自动继承父类的该注解。

Java的三大内置注解

除了上述四种元注解外,JDK 还为我们预定义了另外三种注解,它们是:

  • @Override
  • @Deprecated
  • @SuppressWarnings
  • @Safe Varargs@Safe Varargs("堆污染“警告 java7新增)
  • @FunctionalInterface(函数式接口 java8新增)

@Override 覆盖超类方法

@Override用来指定方法覆载的,它可以强制一个子类必须覆盖父类的方法。

@Deprecated 标记不推荐使用

@Deprecated用于表示某个程序元素(类、方法等)已过时,当其他程序使用已过时的类、方法时,编译器会发出警告。

@SuppressWarnings 压制java的警告

@SuppressWarnings指示被该注解修饰的程序元素以及其所有子元素取消显示指定的编译器警告。

工作原理

「注解的本质就是一个继承了 Annotation 接口的接口」,现在我们就来从虚拟机的层面看看,注解的本质到底是什么。

首先,我们自定义一个注解类型:
在这里插入图片描述
这里我们指定了 Hello 这个注解只能修饰字段和方法,并且该注解永久存活,以便我们反射获取。

之前我们说过,虚拟机规范定义了一系列和注解相关的属性表,也就是说,无论是字段、方法或是类本身,如果被注解修饰了,就可以被写进字节码文件。属性表有以下几种:

  • RuntimeVisibleAnnotations:运行时可见的注解
  • RuntimeInVisibleAnnotations:运行时不可见的注解
  • RuntimeVisibleParameterAnnotations:运行时可见的方法参数注解
  • RuntimeInVisibleParameterAnnotations:运行时不可见的方法参数注解
  • AnnotationDefault:注解类元素的默认值

给大家看虚拟机的这几个注解相关的属性表的目的在于,让大家从整体上构建一个基本的印象,注解在字节码文件中是如何存储的。

所以,对于一个类或者接口来说,Class 类中提供了以下一些方法用于反射注解。

  • getAnnotation:返回指定的注解
  • isAnnotationPresent:判定当前元素是否被指定注解修饰
  • getAnnotations:返回所有的注解
  • getDeclaredAnnotation:返回本元素的指定注解
  • getDeclaredAnnotations:返回本元素的所有注解,不包含父类继承而来的

方法、字段中相关反射注解的方法基本是类似的,这里不再赘述,我们下面看一个完整的例子。

首先,设置一个虚拟机启动参数,用于捕获 JDK 动态代理类。

-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

然后 main 函数。

在这里插入图片描述

我们说过,注解本质上是继承了 Annotation 接口的接口,而当你通过反射,也就是我们这里的 getAnnotation 方法去获取一个注解类实例的时候,其实 JDK 是通过动态代理机制生成一个实现我们注解(接口)的代理类。

我们运行程序后,会看到输出目录里有这么一个代理类,反编译之后是这样的:

在这里插入图片描述
在这里插入图片描述
代理类实现接口 Hello 并重写其所有方法,包括 value 方法以及接口 Hello 从 Annotation 接口继承而来的方法。

而这个关键的 InvocationHandler 实例是谁?

AnnotationInvocationHandler 是 JAVA 中专门用于处理注解的 Handler, 这个类的设计也非常有意思。

在这里插入图片描述
这里有一个 memberValues,它是一个 Map 键值对,键是我们注解属性名称,值就是该属性当初被赋上的值。

在这里插入图片描述
在这里插入图片描述

而这个 invoke 方法就很有意思了,大家注意看,我们的代理类代理了 Hello 接口中所有的方法,所以对于代理类中任何方法的调用都会被转到这里来。

var2 指向被调用的方法实例,而这里首先用变量 var4 获取该方法的简明名称,接着 switch 结构判断当前的调用方法是谁,如果是 Annotation 中的四大方法,将 var7 赋上特定的值。

如果当前调用的方法是 toString,equals,hashCode,annotationType 的话,AnnotationInvocationHandler 实例中已经预定义好了这些方法的实现,直接调用即可。

那么假如 var7 没有匹配上这四种方法,说明当前的方法调用的是自定义注解字节声明的方法,例如我们 Hello 注解的 value 方法。这种情况下,将从我们的注解 map 中获取这个注解属性对应的值。

再总结一下整个反射注解的工作原理:

首先,我们通过键值对的形式可以为注解属性赋值,像这样:@Hello(value = “hello”)。

接着,你用注解修饰某个元素,编译器将在编译期扫描每个类或者方法上的注解,会做一个基本的检查,你的这个注解是否允许作用在当前位置,最后会将注解信息写入元素的属性表。

然后,当你进行反射的时候,虚拟机将所有生命周期在 RUNTIME 的注解取出来放到一个 map 中,并创建一个 AnnotationInvocationHandler 实例,把这个 map 传递给它。

最后,虚拟机将采用 JDK 动态代理机制生成一个目标注解的代理类,并初始化好处理器。

那么这样,一个注解的实例就创建出来了,它本质上就是一个代理类,你应当去理解好 AnnotationInvocationHandler 中 invoke 方法的实现逻辑,这是核心。一句话概括就是,通过方法名返回注解属性值。

感谢大神们的分享:

https://blog.csdn.net/briblue/article/details/73824058

https://juejin.im/post/5b45bd715188251b3a1db54f#heading-2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值