一文搞定java元注解:Target、Retention、Documented、Inherited、Repeatable(JDK1.8新增)

简介: 一文搞定java元注解:Target、Retention、Documented、Inherited、Repeatable(JDK1.8新增)

前言:

在使用SpringBoot构建项目时,项目的启动类中都会有这个注解:SpringBootApplication,查看这个注解的实现我们可以发现这个注解类的实现也使用了很多其他的注解,其中有四个是java提供的注解他们是:Target、Retention、Documented、Inherited。他们就是元注解。


一、元注解



1.什么是元注解


java从1.5开始提供注解的使用,于此同时提供了四个元注解Target、Retention、Documented、Inherited,在java8时又新增了一个元注解Repeatable,到此元注解便有5个了。那什么是元注解呢,我们知道java是支持自定义注解的,所谓元注解就是用来修饰注解类的注解。为什么要用元注解来修饰注解类呢?因为我们总需要一个标志来表明这个注解的一些特性,以及为这个注解类加上一些标识以便在jvm执行被注解的类时来解析注解的意思。事实上被元注解修饰的类默认都会继承Annotation接口。


2.元注解的作用


首先我们需要知道,无论是元注解还是java提供的原生注解亦或者是我们自定义的注解,他们的本质都是一个接口,接口里面提供了属性或者待实现的方法,那我们怎么实现让这个接口变成jvm认识的注解类型呢,其实就是通过元注解来实现的,我们通过元注解来标注当前注解的作用范围,以及生效阶段等等。比如Target这个元注解用来标注,当前注解类可以使用的位置如方法、类、域、包等等。使用Retention注解来标注保留策略,或者叫生命周期也可以等等。这两个基本也是自定义注解都需要使用的注解。


二、五个元注解



每个SpringBoot项目都有个启动类,启动类上都必然会有一个注解:@SpringBootApplication,点进去这个注解的实现就会发现,这个注解的实现便使用了四个最初的元注解,那他们具体的作用是什么呢?


20210616095704968.png


1.Target


元注解Target用来标注注解类(元注解是被用来修饰注解类的,这里说的注解类是指被元注解修饰的注解类)的作用目标,也可以叫标注注解类的使用场所。通过ElementType枚举类来表示该注解类的作用目标,如果是ElementType.TYPE则表示这个注解类可以使用在类、接口、枚举等类型上面,ElementType还有很多枚举,下面就来详细看下这些枚举都代表了什么。


1.1 Target详解


下面列出了ElementType枚举类的源码,共有10种类型,其中前8中是1.5便已经存在,最后两种是1.8开始加入的,前面几种应用场景都比较多,最后新加的两种目前笔者还没有碰到使用场景,笔者提供了汉语释义以帮助理解。


public enum ElementType {
    /** Class, interface (including annotation type), or enum declaration */
    // 可以作用在:类、接口、枚举类上
    TYPE,
    /** Field declaration (includes enum constants) */
    // 可以作用在:域(示例变量)上
    FIELD,
    /** Method declaration */
     // 可以作用在:方法上
    METHOD,
    /** Formal parameter declaration */
     // 可以作用在:方法参数上(方法接收参数时的变量,比如@RequestBody注解)
    PARAMETER,
    /** Constructor declaration */
         // 可以作用在:构造器上
    CONSTRUCTOR,
    /** Local variable declaration */
         // 可以作用在:局部变量上
    LOCAL_VARIABLE,
    /** Annotation type declaration */
         // 可以作用在:注解类型上
    ANNOTATION_TYPE,
    /** Package declaration */
         // 可以作用在:包上
    PACKAGE,
    /**
     * Type parameter declaration
     *
     * @since 1.8
     */
    // 可以作用在:类型参数上,jdk1.8时新增
    TYPE_PARAMETER,
    /**
     * Use of a type
     *
     * @since 1.8
     */
     // 可以作用在:使用类型的任何地方,jdk1.8时新增
    TYPE_USE
}


1.2 Target使用举例


下面列举出几种ElementType枚举类型以及对应的使用场景,若是一个注解类支持多个地方使用,则使用逗号将多个枚举类型隔开即可。比如@Target({ElementType.CONSTRUCTOR, ElementType.METHOD, ElementType.PARAMETER})


ElementType.TYPE :@SpringBootApplication

ElementType.FIELD :@Autowired

ElementType.METHOD :@PostMapping

ElementType.PARAMETER :@RequestBody

ElementType.CONSTRUCTOR :@Autowired

ElementType.ANNOTATION_TYPE :@Documented


2.Retention


Retention的字面意思是保留,该注解的作用其实也是保留。Retention注解作用是用以标注注解类(元注解是被用来修饰注解类的,这里说的注解类是指被元注解修饰的注解类)的保留策略,有些人喜欢叫标注注解类的生命周期,其实这些形容都是可以的,主要是理解就行。那怎么标注注解类的保留策略呢,它也是通过枚举类型来实现声明保留策略的,比如RetentionPolicy.RUNTIME就代表会将将该注解类保留到到运行期间。下面一起看下所有的保留策略。


2.1 Retention详解


下面列出了RetentionPolicy 枚举类的源码,在该枚举类中有三个枚举类型,他们分别代表的意思笔者已经翻译了出来,以供参考,简而言之便是SOURCE代表只在源码中有效,CLASS代表在编译器有效,RUNTIME代表会在运行期有效。从SOURCE到RUNTIME他们的效用是递增的。

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
}


2.2 Retention使用举例


下面列举了RetentionPolicy 中几种枚举类型的使用场景,这些注解大部分开发中都使用过,所以可以帮助我们理解这些元注解的作用,比如dubbo 2.7.7开始使用的DubboReference注解,以及java自己提供的方法重写注解Override。


RetentionPolicy.RUNTIME:@DubboReference

RetentionPolicy.SOURCE:@Override


3.Documented


Document注解与Target和Retention功能点不同,他的使用无需与枚举类配合,直接在需要注解的地方打上注解即可。功能上前面两个注解都与代码的编译有关,Documented的作用只有一点:在使用javadoc生成api文档时会将使用Documented注解类作用的类上加上注解类的信息。详细我们来看下Documented的源码注释。


3.1 Documented详解


下面展示了Documented的源码,源码里有这么一段注释:If a type declaration is annotated with Documented, its annotations become part of the public API of the annotated elements.这段翻译起来不是太费劲还是容易看懂的,意思是:如果一个类型被Documented注解声明了,那么他的所有注解会变成公共api注解的一部分。就是会在api文档里面可见,这也就是Documented的作用了。

/**
 * Indicates that annotations with a type are to be documented by javadoc
 * and similar tools by default.  This type should be used to annotate the
 * declarations of types whose annotations affect the use of annotated
 * elements by their clients.  If a type declaration is annotated with
 * Documented, its annotations become part of the public API
 * of the annotated elements.
 *  * @author  Joshua Bloch
 * @since 1.5
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}


3.2 Documented使用举例


下面列举出几处使用了Documented注解的地方,供参考。


Documented:@SpringBootApplication

Documented:@SpringBootConfiguration

Documented:@EnableAutoConfiguration


4.Inherited


Inherited的意思是继承的,在注解中的作用也是和继承息息相关的,使用的话和Documented类似,也是无需枚举类的配合只需要在需要使用的地方直接打上注解即可,那Inherited注解到底有什么用呢,一起看下他的源码好了。


4.1 Inherited详解


/**
 * Indicates that an annotation type is automatically inherited.  If
 * an Inherited meta-annotation is present on an annotation type
 * declaration, and the user queries the annotation type on a class
 * declaration, and the class declaration has no annotation for this type,
 * then the class's superclass will automatically be queried for the
 * annotation type.  This process will be repeated until an annotation for this
 * type is found, or the top of the class hierarchy (Object)
 * is reached.  If no superclass has an annotation for this type, then
 * the query will indicate that the class in question has no such annotation.
 *  * <p>Note that this meta-annotation type has no effect if the annotated
 * type is used to annotate anything other than a class.  Note also
 * that this meta-annotation only causes annotations to be inherited
 * from superclasses; annotations on implemented interfaces have no
 * effect.
 *  * @author  Joshua Bloch
 * @since 1.5
 * @jls 9.6.3.3 @Inherited
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}


上面展示了Inherited的源码,这注释是真的长,笔者英语功底是真的一般,只能借助翻译软件了。我们来看下第一段的翻译结果:

指示自动继承注释类型。如果
继承的元注释出现在注释类型上
声明,然后用户查询类上的注释类型
声明,并且类声明没有对此类型的注释,
类的父类将自动被查询
注释类型。这个过程将重复进行,直到对此进行注释为止
类型,或类层次结构的顶层(Object)
是达到了。如果没有超类具有此类型的注释,则
查询将表明所涉及的类没有这样的注释。


可以看到翻译软件翻译的还是比较清晰的,大致意思就是若是有类型上出现了继承过来的Inherited注解,则会去查询他的父类是否拥有Inherited注解,父类没有则查询父类的父类直到Object类型。从这段话可以发现Innheried注解是可以被继承的,此外查询机制有些类似于双亲委派模型,都会一直向上操作。再来看一看第二段的解释

请注意,如果被注释的类型用于注释类以外的任何内容,则此元注释类型将不起作用。还要注意,这个元注释只会导致注释从父类继承;已实现接口上的注释不起作用


这段主要就是补充了该注解的应用场景,说明该注解只能使用在注解类上,其他场景使用时没有效果的,也就是说只能使用在注解类上,然后被注解类修饰的类的子类将自动继承该注解。这也就是Inherited的作用了。


4.2 Inherited使用举例


下面列举出几处使用了Inherited注解的地方,供参考:可以发现我们常用的Controller、ResponseBody、RequestMapping等都是没有Inherited注解的,根据Inherited注解的特性可以发现一旦使用了Inherited注解, Controller、ResponseBody、RequestMapping修饰的类的子类也相当于拥有了Controller、ResponseBody、RequestMapping,这样就会不太友好。


Inherited:SpringBootApplication


5.Repeatable注解


这个注解是JDK1.8(也可以叫JDK8)时新增加的元注解,Repeat字面意思是重复的,这个元注解的作用其实和重复的也是息息相关的。详细的解析我们看下Repeatable的源码。


5.1 Repeatable详解


下面展示了Repeatable的源码,从源码中的注释我们可以看到这段话:The value of {@code @Repeatable} indicates the containing annotation type for the repeatable annotation type.意思是:包含该元注解的注解支持重复使用。我们知道如果我们在一个位置加两个@RestController注解,那肯定会报错,但是如果@RestController的实现上有了Repeatable就不会报错了。他的作用就是这个。

/**
 * The annotation type {@code java.lang.annotation.Repeatable} is
 * used to indicate that the annotation type whose declaration it
 * (meta-)annotates is <em>repeatable</em>. The value of
 * {@code @Repeatable} indicates the <em>containing annotation
 * type</em> for the repeatable annotation type.
 *
 * @since 1.8
 * @jls 9.6 Annotation Types
 * @jls 9.7 Annotations
 */
@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();
}


5.2 Repeatable使用举例


Repeatable的使用笔者还没有碰到,因为Repeatable是JDK1.8后加入的,所以目前应用与前四个相比并不是太广。若是想要实战下Repeatable的使用,笔者建议看下这篇文章他写的还是挺好的:查看Repeatable的实战。


三、总结



java中的元注解便是用来修饰注解类的,这也是他们名称的由来。meta-annotaion。这里介绍了五种java元注解,笔者也是做一个总结,希望可以对路过的你有所帮助。


相关文章
|
3月前
|
Java Linux
java基础(3)安装好JDK后使用javac.exe编译java文件、java.exe运行编译好的类
本文介绍了如何在安装JDK后使用`javac.exe`编译Java文件,以及使用`java.exe`运行编译好的类文件。涵盖了JDK的安装、环境变量配置、编写Java程序、使用命令行编译和运行程序的步骤,并提供了解决中文乱码的方法。
86 2
|
1月前
|
Oracle 安全 Java
深入理解Java生态:JDK与JVM的区分与协作
Java作为一种广泛使用的编程语言,其生态中有两个核心组件:JDK(Java Development Kit)和JVM(Java Virtual Machine)。本文将深入探讨这两个组件的区别、联系以及它们在Java开发和运行中的作用。
82 1
|
1月前
|
IDE Java 编译器
开发 Java 程序一定要安装 JDK 吗
开发Java程序通常需要安装JDK(Java Development Kit),因为它包含了编译、运行和调试Java程序所需的各种工具和环境。不过,某些集成开发环境(IDE)可能内置了JDK,或可使用在线Java编辑器,无需单独安装。
78 1
|
2月前
|
缓存 Java Maven
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
如何解决Java项目中因JDK版本不匹配导致的编译错误,包括修改`pom.xml`文件、调整项目结构、设置Maven和JDK版本,以及清理缓存和重启IDEA。
63 1
java: 警告: 源发行版 11 需要目标发行版 11 无效的目标发行版: 11 jdk版本不符,项目jdk版本为其他版本
|
2月前
|
设计模式 Java API
[Java]静态代理与动态代理(基于JDK1.8)
本文介绍了代理模式及其分类,包括静态代理和动态代理。静态代理分为面向接口和面向继承两种形式,分别通过手动创建代理类实现;动态代理则利用反射技术,在运行时动态创建代理对象,分为JDK动态代理和Cglib动态代理。文中通过具体代码示例详细讲解了各种代理模式的实现方式和应用场景。
38 0
[Java]静态代理与动态代理(基于JDK1.8)
|
2月前
|
数据采集 存储 安全
Java零基础-replace(CharSequence target, CharSequence replacement)详解
【10月更文挑战第8天】Java零基础教学篇,手把手实践教学!
28 1
|
2月前
|
Java
让星星⭐月亮告诉你,jdk1.8 Java函数式编程示例:Lambda函数/方法引用/4种内建函数式接口(功能性-/消费型/供给型/断言型)
本示例展示了Java中函数式接口的使用,包括自定义和内置的函数式接口。通过方法引用,实现对字符串操作如转换大写、数值转换等,并演示了Function、Consumer、Supplier及Predicate四种主要内置函数式接口的应用。
32 1
|
2月前
|
Java
Java基础之 JDK8 HashMap 源码分析(中间写出与JDK7的区别)
这篇文章详细分析了Java中HashMap的源码,包括JDK8与JDK7的区别、构造函数、put和get方法的实现,以及位运算法的应用,并讨论了JDK8中的优化,如链表转红黑树的阈值和扩容机制。
39 1
|
3月前
|
Oracle Java 关系型数据库
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
如果遇到"exec format error"问题,文章建议先检查Linux操作系统是32位还是64位,并确保安装了与系统匹配的JDK版本。如果系统是64位的,但出现了错误,可能是因为下载了错误的JDK版本。文章提供了一个链接,指向Oracle官网上的JDK 17 Linux版本下载页面,并附有截图说明。
Linux下JDK环境的配置及 bash: /usr/local/java/bin/java: cannot execute binary file: exec format error问题的解决
|
3月前
|
Java 编译器 程序员
Java注解,元注解,自定义注解的使用
本文讲解了Java中注解的概念和作用,包括基本注解的用法(@Override, @Deprecated, @SuppressWarnings, @SafeVarargs, @FunctionalInterface),Java提供的元注解(@Retention, @Target, @Documented, @Inherited),以及如何自定义注解并通过反射获取注解信息。
Java注解,元注解,自定义注解的使用