虽然在平时开发中经常使用注解,却不知道如何自定义一个注解类型以及注解的实现原理。抽时间学习了一下,记录下来加深理解。
1. 注解是什么
之前看到一篇文章将注解理解为“标签”,感觉还是比较贴切的。我们可以把注解理解为给包、类、方法、字段打的一个标签,并利用java的反射机制对注解标注的类、方法或者字段进行相应的处理。
2. 定义注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String name() default "";
}
利用关键字@interface声明一个注解类型,并利用@Retention、@Target、@Document、@Inherited等元注解对注解进行定义。
- @Retention
用于自定义注解类型的元注解。retention的意思是保留,@Retention用来定义自定义注解有效期。有三种取值:
public enum RetentionPolicy {
/**
* 注解只在源码文件中保留,不会被编译器编译
*/
SOURCE,
/**
* 会被编译到生成的class文件中,但不会在运行时保留
*/
CLASS,
/**
* 运行时有效,可以通过反射机制读取到该注解定义
*/
RUNTIME
}
- @Target
用于定义注解的使用目标。我们知道注解可以用在包、类、方法、字段等字面量上,但不能将类的注解用到方法上,JDK使用@Target定义注解的使用目标。在java.lang.annotation.ElementType 枚举类型中定义了若干注解目标枚举常量:TYPE、FIELD、METHOD、PARAMETER、CONSTRUCTOR、LOCAL_VARIABLE等。
ElementType.TYPE 用于定义类、接口、枚举注解;
ElementType.METHOD 用于定义方法注解;
ElementType.FIELD 用于定义字段注解;
ElementType.CONSTRUCTOR 用于定义构造器注解; - @Document
这个元注解比较简单,用于将该注解包含在Javadoc中 - @Inherited
在定义注解时加上@Inherited表示该注解具有"继承性",这里的继承性是指如果父类使用了自定义注解,则子类也继承了该自定义注解。举例说明:
/**
* 自定义注解AnnotationDemo、使用@Inherited表明该注解注解具有继承性
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String name() default "";
}
/**
* 使用自定义注解修饰的父类
*/
@AnnotationDemo(name = "小明")
public class Super {
public void hello(){
System.out.println("I am super!");
}
}
/**
* 子类继承Super超类
* 由于Super被@AnnotationDemo注解修饰,Child子类自动被@AnnotationDemo修饰,
*
*/
public class Child extends Super {
public void say(){
System.out.println("I am child!");
}
}
3. 注解的属性
在自定义注解时可以为注解定义若干属性,如在上边定义的@AnnotationDemo注解中定义了一个名为name的属性,默认值为""。注解类型只有属性没有方法,且注解属性声明为属性名+(),属性类型可以为基本类型、String、Class、Enum等类型。注解的属性使用default关键字声明该属性的默认值,如果不为属性声明默认值,则必须在使用注解时为该属性赋值。
特殊的:如果属性名为value,在使用注解时可以不指定属性名赋值。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Inherited
public @interface AnnotationDemo {
String value() default "";
}
/**
* @AnnotationDemo注解的属性名为value,使用时可以不指定属性名为属性赋值
* 等价于@AnnotationDemo(value="super")
*/
@AnnotationDemo("super")
public class Super {
public void hello(){
System.out.println("hello world!");
}
}
如果注解定义了多个属性,使用时不同的属性赋值用逗号分隔。
4. 注解与反射
正如在本文开头所说的注解相当于给类、方法、字段打的标签,对于运行时有效的注解需要通过JDK提供的反射机制来让标签起作用。下面通过一个简单的例子来演示如何利用反射机制使注解起作用。
- 自定义一个注解@Run
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Run {
}
- 使用定义的注解标注方法
public class RunTest {
@Run
public void test1(){
System.out.println("run test1");
}
public void test2(){
System.out.println("run test2");
}
}
- 通过反射API对@Run标记的方法执行调用
public class Main {
public static void main(String[] args) throws ClassNotFoundException{
try {
Class clazz = Class.forName("com.annotation.demo.test.RunTest");
Method[] methods = clazz.getMethods();
RunTest junitTest =(RunTest) clazz.newInstance();
for (Method method : methods){
if (method.isAnnotationPresent(Run.class)){
method.setAccessible(true);
method.invoke(junitTest);
}
}
}catch (ClassNotFoundException e){
e.printStackTrace();
}catch (InstantiationException e){
e.printStackTrace();
}catch (IllegalAccessException e){
e.printStackTrace();
}catch (InvocationTargetException e){
e.printStackTrace();
}
}
}
在这个例子中首先定义了一个@Run注解,并在RunTest中定义了两个方法。其中一个方法使用了@Run进行注解,另一个方法没有添加@Run注解,利用反射API对使用了@Run注解的方法进行调用。输出结果也正如我们所料:只有RunTest中的test1方法被执行。
OK,java注解的介绍就到这里。
你的关注是我持续更新的动力!