注解的原理这一块,在面试中也是经常会问到了,很是很精髓的,它是java5引入的,用来为类、方法、字段、参数等Java结构提供额外信息的机制。比如,Java核心类库,@Override
注解是被用来声明某个实例方法重写了父类的同名同参数的类型的方法。
@Override
本身被两个注解所标注,其中,@Target
用来限定目标注解所能标注的Java结构,@Override
只能用来标注方法。
Rentention
则是用来限定当前生命周期。注解共有三种不同的声明周期:SOURCE,CLASS和RUNTIME分别表示:注解只出现在源代码中,只出现在源代码与字节码中,只出现在源代码,字节码和运行过程中。
这里@Override
只能出现在源代码中,所以一旦被标注了这个注解的方法被编译为字节码的时候,就会被删除。
换句话说,在debug之前,@Override
可以检测方法是否为重写,当debug的时候又会自动失效。
同时,注解也是可以自定义的,注解在应用程序中,以插件的形式被编译,而注解也就被称为注解处理器(annotation processor)
接下来主要介绍一下注解处理器的原理。
注解处理器(annotation processor)
首先来看一下Java程序的编译流程:
如上图所示,Java源代码将编译分为三个过程:
- 将源文件解析为抽象语法书
- 调用已注册的注解处理器
- 生成字节码
如果在第二部因为调用注解处理器而产生源文件了,那就会循环执行1 2 步,最终生成字节码。
@Target({Element.Type.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface CheckGetter{
}
上面这段代码定义了一个注解@CheckGetter。可以被用来标注类和字段,但是生命周期也是在编译的时候就会自动清除。
下面有一个处理@CheckGetter
注解的处理器,它将遍历被标注的类中的实例,并且检查是否有getter方法。
public interface Processor{
void init(ProcessingEnvironment processingEnv);
Set<String> getSupportedAnnotationTypes();
SourceVersion getSupportedSourceVersion();
boolean process(Set<? extends TypeElement> annotations,RoundEnvironment roundEnv);
...
}
所有的注解都实现这个Processor接口。之所以这里用init来存放注解处理器的初始化方法代码,而不用构造器,是因为注解处理器的实例是通过反射的API生成的,也正是因为使用了反射的API,所以每个注解处理器都需要定义一个无参构造。
通常来说,当编写注解处理器的时候,不需要声明任何构造器,并且依赖于Java编译器,为之插入一个无参数构造器,而具体的初始化代码,则放入init方法中。
而剩下的三个方法,getSupportedAnnotationTypes将返回注解处理器支持的注解类型;getSupportedSourceVersion方法将会返回处理器支持的java版本,通常这个版本要和编译器版本保持一致,而process方法则是最关键的注解处理方法。
而JDK内部封装了一个实现Processor接口的抽象类AbstractProcessor,该抽象类实现了init,getSupportedAnnotationTypes和getSupportSourceVersion方法。
boolean process(
Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv
);
其中,最后一个方法,也就是process方法,会涉及到不同种类的Element,分别指代Java程序的不同结构。如TypeElement指代类或者接口,VariableElement指代字段,局部变量,enum常量等,ExecutableElement指代方法或者构造器。