前言:
在使用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,点进去这个注解的实现就会发现,这个注解的实现便使用了四个最初的元注解,那他们具体的作用是什么呢?
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元注解,笔者也是做一个总结,希望可以对路过的你有所帮助。