1 注解的基本概念
1.1 描述
自定义注解是指开发人员可以根据自己的需求定义一些标记,用来标识某些类、方法、变量等,在代码编写和执行的过程中,可以读取这些注解信息,从而实现一些特定的功能。注解是一种元数据,它不直接影响程序代码的执行,但可以为代码提供额外的信息或指示,使代码具有更多的灵活性和可扩展性。在Java中,注解的使用非常广泛,常见的注解包括:@Override、@Deprecated、@SuppressWarnings等。自定义注解的语法格式为:@interface 自定义注解名 {}。需要注意的是,自定义注解也可以定义属性,语法格式为:@interface 自定义注解名 {数据类型 属性名() default 默认值;}。在使用自定义注解时,需要调用注解类的属性方法来获取注解的属性值。
1.2 使用范围
这里是引用注解可以在Java程序中的类、方法、构造函数、成员变量、参数等多个位置使用,并且可以限定使用范围来增强注解的作用力。常见的注解使用范围如下:
@Target注解:该注解用于限定注解的使用范围,可以设置的取值有CONSTRUCTOR、FIELD、LOCAL_VARIABLE、METHOD、PACKAGE、PARAMETER、TYPE等。
@Retention注解:该注解指定了注解的保存方式,即生命周期,可以设置的取值有SOURCE、CLASS、RUNTIME,其中RUNTIME表示注解信息在运行时可以通过反射获取。
@Documented注解:该注解用于指定注解是否包含在Java文档中。
@Inherited注解:该注解表明被注解的类可以被子类继承,这样子类就具有了同样的
2 如何自定义注解
Java语言允许开发者自定义注解,自定义注解的语法格式为:@interface 注解名 { }
其中,@interface表示该语法元素是一个注解声明,注解名就是自定义注解的名称,在{}内部定义的是注解中包含的元素,可以定义多个元素,元素的定义语法格式为: 数据类型 元素名称() default 默认值;
举个例子,假设我们要定义一个注解,用于标记方法的作者和版本号:
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodInfo {
String author() default "Unknown";
String version() default "1.0";
}
注:@interface 在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。
这样就定义了一个名为MethodInfo的注解,其中包含了两个元素:作者和版本号,并且都有默认值。可以在使用该注解时,给元素传入具体的值,例如:
@MethodInfo(author = "张三", version = "1.2")
public void doSomething() {
// 方法体
}
通过这种方式,就可以自定义注解并在代码编写和执行过程中使用它们了。
3 常用元注解
@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();
}
源码中可以看出,需要配置属性。ElementType:表示自定义注解所能够修饰的对象类型
public enum ElementType {
/** 类,接口(包括注解类型)或枚举的声明 */
TYPE,
/** 属性的声明 */
FIELD,
/** 方法的声明 */
METHOD,
/** 方法形式参数声明 */
PARAMETER,
/** 构造方法的声明 */
CONSTRUCTOR,
/** 局部变量声明 */
LOCAL_VARIABLE,
/** 注解类型声明 */
ANNOTATION_TYPE,
/** 包的声明 */
PACKAGE,
/** 类型参数声明(jdk1.8加入) */
TYPE_PARAMETER,
/** 任意类型(jdk1.8加入) */
TYPE_USE
}
@Retention
在上述例子中,可以看到自定义注解上使用@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 {
/** 注解将被编译器忽略掉 */
SOURCE,
/** 注解将被编译器记录在class文件中,但在运行时不会被虚拟机保留,这是一个默认的行为 */
CLASS,
/** 注解将被编译器记录在class文件中,而且在运行时会被虚拟机保留,因此它们能通过反射被读取到 */
RUNTIME
}
详解:
1、RetentionPolicy.SOURCE:注解将被限定在Java源文件中,那么这个注解即不会参与编译也不会在运行期起任何作用,这个注解就和一个注释是一样的效果。只在java文件中存在。
2、RetentionPolicy.CLASS,注解将被编译到Class文件中,那么编译器可以在编译时根据注解做一些处理动作,但是运行时JVM(Java虚拟机)会忽略它,我们在运行期也不能读取到。
3、RetentionPolicy.RUNTIME,那么这个注解可以在运行期的加载阶段被加载到Class对象中。那么在程序运行阶段,我们可以通过反射得到这个注解,并通过判断是否有这个注解或这个注解中属性的值,从而执行不同的程序代码段。我们实际开发中的自定义注解几乎都是使用的RetentionPolicy.RUNTIME。
@Document
@Documented注解,是被用来指定自定义注解是否能随着被定义的java文件生成到JavaDoc文档当中。
@Inherited
@Inherited注解,是指定某个自定义注解如果写在了父类的声明部分,那么子类的声明部分也能自动拥有该注解。@Inherited注解只对那些@Target被定义为ElementType.TYPE的自定义注解起作用。
4 反射获取注解
public class TestAnnotation {
public static void main(String[] args) {
try {
Class clazz = Class.forName("com.htf.aggregate.controller.WorkTaskController");
Method method = clazz.getMethod("createTask", WorkTaskAddUpdateRequest.class);
if(method.isAnnotationPresent(OperateLog.class)){
System.out.println("WorkTaskController类上配置了OperateLog注解!");
//获取该元素上指定类型的注解
OperateLog operateLog = method.getAnnotation(OperateLog.class);
System.out.println("operateType: " + operateLog.operateType() +
", module: " + operateLog.module() +
", operationDetail: " + operateLog.operationDetail());
}else{
System.out.println("WorkTaskController类上没有配置OperateLog注解!");
}
}catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}
解释:
1、isAnnotationPresent(Class<? extends Annotation> annotationClass)方法是专门判断该元素上是否配置有某个指定的注解;
2、getAnnotation(Class annotationClass)方法是获取该元素上指定的注解。之后再调用该注解的注解类型元素方法就可以获得配置时的值数据;
3、也可以使用 getAnnotations() 获取所有注解。