前言
在使用Spring Boot的时候,大量使用注解的语法去替代XML配置文件,十分好用。
然而,在使用注解的时候只知道使用,却不知道原理。直到需要用到自定义注解的时候,才发现对注解原理一无所知,所以要学习一下。
特别注意的是,注解是Java提供的语法,而不是Spring特殊的语法。Java自5.0版本开始引入注解之后,注解就成为了Java平台中非常重要的一部分。
什么是注解(Annotation)
注解(Annotation)是一种应用于类、方法、参数、变量、构造器及包声明中的特殊修饰符。它是一种由JSR-175标准选择用来描述元数据的工具。
用一个词来描述注解,那就是元数据(描述数据的数据)。 那么就可以说,注解是源代码的元数据。
@Override public String toString() { return "良辰美景奈何天"; }
在上面的代码中,我重写了toString()方法并在方法上使用了@Override注解。但是,即使我不使用@Override注解标记代码,程序也能正常执行。那么为什么还要使用注解呢?事实上,@Override注解会告诉编译器这个方法是一个重写的方法(描述方法的元数据),如果父类中不存在该方法,编译器便会报错,提示该方法没有重写父类中的方法。但是如果我不小心拼写错误,例如将toString()写成了toSpring(),而且我也没有使用@Override注解,那么程序依然能够编译运行,虽然运行结果会和我期望的大不相同。到这里我们就可以明白什么是注解,还有使用注解既可以帮助编译器检查代码,也可以帮助我们阅读程序。
注解的用途
注解仅仅是元数据,和业务逻辑无关。但是元数据的用户可以通过读取这些元数据并实现必要的逻辑(第三方代码,注解不能干扰源代码的编译运行),以此来附加相应的业务操作。
生成文档。比如我们用的IDE里面会自动加上的@param,@return,@author等注解。
编译时检查格式。比如@Override,@SuppressWarnings等注解。
跟踪代码依赖性,实现替代配置文件功能。比如@Configuration,@Bean等注解。
Java类库提供的元注解
Java类库中提供了java.lang.annotation包,包中包含了元注解和接口,定义自定义注解所需要的所有内容都在这个包中。
比如java.lang.annotation.Annotation是所有注解自动继承的接口,不需要定义时指定,类似于所有类都自动继承Object一样。
@Documented,@Retention,@Target,@Inherited是Java提供的4个元注解。
@Documented注解表示将此注解包含在javadoc中。
@Documented // 生成文档 @Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息 @Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE是可注解在注解定义上 public @interface Documented { }
使用此注解的类会被javadoc工具提取成文档。在doc文档中的内容会因为此注解的信息内容不同而不同,相当于@see,@param等。
@Retention注解表示在什么级别保留该注解信息。
@Documented // 生成文档 @Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别保留注解信息 @Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE表示是可注解在注解定义上 public @interface Retention { 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文件中可用,但会被VM抛弃 */ 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. * VM将在运行期也保留注释,因此可以通过反射机制读取注解信息 * @see java.lang.reflect.AnnotatedElement */ RUNTIME }
@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(); // 注解的目标位置,必须参数(无默认值) }
可能的参数值在枚举类型ElementType中。
public enum ElementType { /**
* Class, interface (including annotation type), or enum declaration
* 类,接口(包括注解类型)或enum声明
*/ TYPE, /**
* Field declaration (includes enum constants)
* 域声明(包括enum实例)
*/ 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 }
@Inherited注解表示允许子类继承父类中的注解。
@Documented // 生成文档 @Retention(RetentionPolicy.RUNTIME) // 注解在运行期级别上保留注解信息 @Target(ElementType.ANNOTATION_TYPE) // 注解放置的目标位置,ANNOTATION_TYPE是可注解在注解定义上 public @interface Inherited { }
自定义注解
使用@interface关键字来自定义注解时,自动继承java.lang.annotation.Annotation接口(隐式继承),由编译程序自动完成其它细节。在定义注解时,不能显式继承其它的注解或接口。@interface关键字用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum),可以通过default来声明参数的默认值。
定义注解的通用格式:
public @interface 注解名 { 访问修饰符 返回值类型 参数名() default 默认值; }
注解参数成员的可支持数据类型:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组
注解参数的规范:
1.只能用public或默认(default)这两个访问修饰符修饰。一般使用default,即不指定访问修饰符。
2.参数成员只能用上面列出的、注解参数支持的数据类型。
3.如果只有一个参数成员,最好把参数名称设为value。
简单使用例子:
首先定义注解。
@Documented @Target({ElementType.METHOD, ElementType.TYPE}) // 注解放置的目标位置,这里可以添加在METHOD上,也可以添加在TYPE上 @Retention(RetentionPolicy.RUNTIME) // 注解在运行期保留注解信息,在运行期可以通过反射获取注解信息 public @interface MyAnnotation { String name() default ""; // 姓名,默认值空字符串 String sex() default "男"; // 性别,默认值男 }
将注解使用在类上。
@MyAnnotation(name = "谢小猫") public class MyAnnotationOnClass { }
将注解使用在方法上。
public class MyAnnotationOnMethod { @MyAnnotation(name = "赵小虾") public void zhaoShrimp() { } @MyAnnotation(name = "李小猪", sex = "女") public void liPig() { } }
最后通过反射获取注解信息并打印出来。
public class MyAnnotationTest { public static void main(String[] args) { // 调用Class的isAnnotationPresent方法,检查类MyAnnotationUse是否含有@AnnotationTest注解 if (MyAnnotationOnClass.class.isAnnotationPresent(MyAnnotation.class)) { // 若存在就获取注解 MyAnnotation myAnnotation = MyAnnotationOnClass.class.getAnnotation(MyAnnotation.class); System.out.println("完整注解:" + myAnnotation); // 获取注解属性 System.out.println("性别:" + myAnnotation.sex()); System.out.println("姓名:" + myAnnotation.name()); System.out.println("---------------------------------------------------------"); } // 调用Class的getDeclaredMethods方法,获取MyAnnotationUse的所有方法声明 Method[] methods = MyAnnotationOnMethod.class.getDeclaredMethods(); for (Method method : methods) { System.out.println("方法声明:" + method); // 调用Method的isAnnotationPresent方法,检查方法是否含有@AnnotationTest注解 if (method.isAnnotationPresent(MyAnnotation.class)) { MyAnnotation myAnnotationInMethod = method.getAnnotation(MyAnnotation.class); System.out.println("方法名:" + method.getName() + ",姓名:" + myAnnotationInMethod.name() + ",性别:" + myAnnotationInMethod.sex() + ")"); } } } }
查看结果。
通过上面的简单使用例子,我们对注解的使用基本有些了解了:注解的使用与反射是离不开的。我们可以通过Java反射机制读取注解的信息,并根据这些信息更改目标程序的逻辑。
这样,我们就可以利用代码中的注解,达到间接控制程序代码运行的目的。
注解的总结
1.Java中所有注解都隐式继承(自动继承)于java.lang.annotation.Annotation,注解不允许显式继承其他接口。
2.注解不能直接干扰程序代码的运行,无论增加或删除注解,代码都能够正常运行。Java语言解释器会忽略这些注解,而由第三方工具负责对注解进行处理。
3.一个注解可以拥有多个成员,成员声明和接口方法声明类似,成员声明有规范限制。