文章目录
注解简介
Java 注解(Annotation)又称 Java 标注,是 JDK5.0 引入的一种注释机制。
Java 语言中的类、方法、变量、参数和包等都可以被标注。和 Javadoc 不同,Java 标注可以通过反射获取标注内容。在编译器生成类文件时,标注可以被嵌入到字节码中。Java 虚拟机可以保留标注内容,在运行时可以获取到标注内容 。 当然它也支持自定义 Java 标注。
注解的意义
Java 注解用于为 Java 代码提供元数据。作为元数据,注解不直接影响你的代码执行,但也有一些类型的注解实际上可以用于这一目的。处理获取注解值的过程是我们开发者直接写的注解提取逻辑,处理提取和处理 Annotation 的代码统称为APT(Annotation Processing Tool),主要可实现以下功能:
-
提供信息给编译器: 编译器可以利用注解来检测出错误或者警告信息,打印出日志。
-
编译阶段时的处理: 软件工具可以用来利用注解信息来自动生成代码、文档或者做其它相应的自动处理。
-
运行时处理: 某些注解可以在程序运行的时候接受代码的提取,自动做相应的操作。
-
代码可读性:注解语法很简单,除了使用@开头,它基本与java基础语法一致。通常我们通过注解名称就可以简单了解到注解的作用,比如@Test,@Override,@Bean 等。
内置的注解
作用在代码的注解(在 java.lang 中)是
- @Override - 标注该方法是重写方法。如果其父类或者接口中并没有该方法时,会报编译错误。
- @Deprecated - 标记过时的或者不建议使用的api。如果使用该方法,会报编译警告。
- @SuppressWarnings - 指示编译器去忽略注解中声明的警告。若是注释的api范围内存在警告则可以忽略告警,但是需要填写需要压制的报警类型,若存在多个警告,可以通过配置数组形式进行压制。
作用在其他注解的注解(或者说 元注解,在 java.lang.annotation)是:
- @Retention - 标识这个注解怎么保存,是只在代码中,还是编入class文件中,或者是在运行时可以通过反射访问。
- @Documented - 标记这些注解是否包含在用户文档中。
- @Target - 标记这个注解应该是哪种 Java 成员。
- @Inherited - 它所标注的Annotation将具有继承性。
从 Java 7 开始,额外添加了 3 个注解:
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
从 Java 7 开始,额外添加了 3 个注解:
- @SafeVarargs - Java 7 开始支持,忽略任何使用参数为泛型变量的方法或构造函数调用产生的警告。
- @FunctionalInterface - Java 8 开始支持,标识一个匿名函数或函数式接口。
- @Repeatable - Java 8 开始支持,标识某注解可以在同一个声明上使用多次。
注解属性类型
注解属性类型可以有以下列出的类型
- 1.基本数据类型
- 2.String
- 3.枚举类型
- 4.注解类型
- 5.Class类型
- 6.以上类型的一维数组类型
Annotation的使用范围
默认情况下一个自定义的Annotation可以在任何方法、属性以及类上使用。若想限定Annotation的使用范围可以使用@Target注解。
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
public String key() default "default_key";
public String value() default "default_value";
}
ElementType的取值 | 注解使用范围 |
---|---|
METHOD | 可用于方法上 |
TYPE | 可用于类或者接口上 |
ANNOTATION_TYPE | 可用于注解类型上(被@interface修饰的类型) |
CONSTRUCTOR | 可用于构造方法上 |
FIELD | 可用于域上 |
LOCAL_VARIABLE | 可用于局部变量上 |
PACKAGE | 用于记录java文件的package信息 |
PARAMETER | 可用于参数上 |
注解源码解析
Annotation
@interface
使用 @interface 定义注解时,意味着它实现了 java.lang.annotation.Annotation 接口,即该注解就是一个Annotation。
定义 Annotation 时,@interface 是必须的。
注意:它和我们通常的 implemented 实现接口的方法不同。Annotation 接口的实现细节都由编译器完成。通过 @interface 定义注解后,该注解不能继承其他的注解或接口。
- 注解的本质就是一个
Annotation
接口,我们使用的其它注解默认都继承了Annotation
- 每 个
Annotation
都与 1个RetentionPolicy
关联,并且与 多个ElementType
关联。
package java.lang.annotation;
/**Annotation接口源码*/
public interface Annotation {
boolean equals(Object obj);
int hashCode();
String toString();
//注解类型
Class<? extends Annotation> annotationType();
}
ElementType
用来指定注解类型的枚举类:
package java.lang.annotation;
public enum ElementType {
TYPE, /* 类、接口(包括注释类型)或枚举声明 */
FIELD, /* 字段声明(包括枚举常量) */
METHOD, /* 方法声明 */
PARAMETER, /* 参数声明 */
CONSTRUCTOR, /* 构造方法声明 */
LOCAL_VARIABLE, /* 局部变量声明 */
ANNOTATION_TYPE, /* 注释类型声明 */
PACKAGE /* 包声明 */
}
RetentionPolicy
用来表示注解的保留策略的枚举类:
package java.lang.annotation;
public enum RetentionPolicy {
SOURCE, /* Annotation信息仅存在于编译器处理期间,编译器处理完之后就没有该Annotation信息了 例如,使用@Override修饰一个方法的时候,就意味着该方法覆盖父类的方法;并且在编译期间会进行语法检查!编译器处理完后,@Override就会被抹去。 */
CLASS, /* 编译器将Annotation存储于类对应的.class文件中。默认行为,用于自动生成代码或文档等 */
RUNTIME /* 编译器将Annotation由JVM读入java内存中存放,通常我们使用自定义的注解就需要指定该策略*/
}
下面我们主要讲下JDK提供的元注解相关源码:
Retention
用来标识一个注解的保存策略,是只在java源代码中,还是编入.class文件中,或者是在运行时可以通过反射在java内存中访问。其中RetentionPolicy
是 Annotation
的策略属性,而 @Retention 的作用,就是配合RetentionPolicy
指定 Annotation
的策略属性。
@Retention(RetentionPolicy.RUNTIME) 的意思就是指定该 Annotation 的策略是 RetentionPolicy.RUNTIME。这就意味着,编译器会将该 Annotation 信息加载到JVM运行时数据区中。
定义 Annotation 时,@Retention 可有可无,通常自定义注解使用RetentionPolicy.RUNTIME。若没有 @Retention,则默认是 RetentionPolicy.CLASS。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* 返回保留策略的值,在RetentionPolicy的实例范围中取值
*/
RetentionPolicy value();
}
Documented
标记这些注解是否包含在用户文档中。指示默认情况下,javadoc和类似工具将记录具有类型的注释。此类型应用于注释其注释会影响客户端使用注释元素的类型的声明。如果使用Documented对类型声明进行注释,则其注释将成为注释元素的公共API的一部分。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
Target
标记这个注解应该是哪种 Java 成员。前面我们说过,ElementType
是 Annotation
的类型属性。而 @Target 的作用,就是来指定 Annotation
的类型属性。
@Target(ElementType.TYPE) 的意思就是指定该 Annotation
的类型是 ElementType.TYPE
。这就意味着,该注解类是来修饰"类、接口(包括注解类型)或枚举声明"的注解。
定义 Annotation 时,@Target 可有可无。若有 @Target,则该 Annotation 只能用于它所指定的地方;若没有 @Target,则该 Annotation 可以用于任何地方。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
*返回可应用于注解类型的元素的数组
*/
ElementType[] value();
}
Inherited
它所标注的Annotation将具有继承性。举个例子,某个类 Base 使用了MyAnnotation,则 BaseClass 被注解 了MyAnnotation的注解;如果另一个类OtherClass继承了 BaseClass ,如果 MyAnnotation 上包含 @Inherited的注解,则OtherClass也具有了注解 MyAnnotation,反正则不会被MyAnnotation所注解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
注解的语法要求
- 使用@interface关键字定义注解
- 成员以无参无异常方式声明
- 可以用default为成员指定一个默认值
- 如果注解只有一个成员,则成员名必须取名value(),在使用时可以忽略成员名和赋值
号(=); - 成员类型是受限的,合法的类型包括原始类型String,Class,Annotation,Enumeration
- 注解类可以没有成员,没有成员的注解称为标识注解
自定义注解
示例如下:
public @interface MyAnnotation {
}
//注解一个接口
@MyAnnotation
interface inter{
}
//注解一个注解
@MyAnnotation
@interface MyAnnotationA{
}
//注解在属性上
@MyAnnotation
class Info{
//注解在属性上
@MyAnnotation
public static String desc = "info";
//注解在构造器上
@MyAnnotation
Info(){
System.out.println("InheritableFather:"+Info.class.isAnnotationPresent(Inherited.class));//false
}
//注解在方法和参数上
@MyAnnotation
public static void main(@MyAnnotation String[] args) {
System.out.println(desc);
}
}
在一个Annotation中也可以定义多个属性:
public @interface MyAnnotation {
public String key();
public String value();
}
@MyAnnotation(key="my",value = "Anntotation")
class Info{
}
若是想为Annotation设置默认值可以使用default关键字
public @interface MyAnnotation {
public String key() default "default_key";
public String value() default "default_value";
}
@MyAnnotation
class Info{
}
如果想要限制Annotation属性的取值范围,可以使用枚举
public @interface MyAnnotation {
public String key() default "default_key";
public String value() default "default_value";
public Color color() default Color.RED;
}
@MyAnnotation(color = Color.BLUE)
class Info{
}
enum Color{
RED,GREEN,BLUE;
}
使用元注解去注解一个注解:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface MyAnnotation {
}
注解与反射机制
有关Java反射机制的原理请参考这篇博文Java反射机制
通常我们的自定义注解若想取得作用,必然需要结合反射机制实现其意义。通过反射可以取得属性、方法、构造方法以及类上的所有@Retention值为RetentionPolicy.RUNTIME的注解内容。在Filed、Method、Construct、Class类中全部实现了AnnotatedElement接口,通过Annotation[] getAnnotations();
方法便可以获取。其API如下:
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
public String key() default "default_key";
public String value() default "default_value";
}
@MyAnnotation
class Info extends Thread{
@Override
@Deprecated
@SuppressWarnings({"deprecated","unused"})
@MyAnnotation
public void run() {
List list = new ArrayList();
}
}
测试如下:
@Test
public void test1() throws NoSuchMethodException {
Class cs = Info.class;
//获取run方法对象
Method method = cs.getMethod("run");
//获取run方法上注解,注意只能得到@Retention值为RetentionPolicy.RUNTIME的注解
Annotation[] annotations = method.getAnnotations();
for(Annotation an : annotations){
System.out.println(an.toString());
//判断是否是指定Annotation
}
//判断是否包含指定Annotation
if(method.isAnnotationPresent(MyAnnotation.class)){
//获取指定Annotation
MyAnnotation myAnnotation = method.getAnnotation(MyAnnotation.class);
//获取Annotation的属性
String key = myAnnotation.key();
String value = myAnnotation.value();
System.out.println(key+" --> "+value);
}
}
第三方框架中注解的应用
- 作为一个服务器应用的开发者,平常我们所使用的第三方框架如mvc框架SSM、测试框架junit、微服务框架Spring Cloud、日志框架log4j等等都有注解的应用,如果我们要熟练使用和理解这些框架的原理,则注解的基础知识则是必不可少的。
我们以SpringBoot为例:
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
其**@SpringBootApplication**注解源码如下:
//下面四个就是元注解
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
//下面三个就是SpringBoot自定义的注解,想了解的同学请自行查看源码
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {@Filter(type = FilterType.CUSTOM,classes = {TypeExcludeFilter.class}),
@Filter(
type = FilterType.CUSTOM,
classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
//@AliasFor是一个注解,用于为注解属性声明别名
@AliasFor(annotation = EnableAutoConfiguration.class)
Class<?>[] exclude() default {};
@AliasFor(annotation = EnableAutoConfiguration.class)
String[] excludeName() default {};
@AliasFor(annotation = ComponentScan.class,attribute = "basePackages")
String[] scanBasePackages() default {};
@AliasFor(annotation = ComponentScan.class,attribute = "basePackageClasses")
Class<?>[] scanBasePackageClasses() default {};
}