转载请注明出处:https://blog.youkuaiyun.com/jiyisuifeng222/article/details/117464197
本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 情花打雪 即可关注,每个工作日都有文章更新。
注解的原理介绍
1.注解的声明周期:
一般如果需要在运行时去动态获取注解信息,那只能用 RUNTIME 注解,比如@Deprecated使用RUNTIME注解;
如果要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife),就用 CLASS注解;
如果只是做一些检查性的操作,比如 @Override 和 @SuppressWarnings,使用SOURCE 注解。
2.编译期注解的原理
由编译期扫描到有@Override等注解的类,在编译器的注解处理器进行代码检查。检查涉及到的原理为APT技术。
3.运行时注解的原理:
以Spring中的@Component注解为例:
所有“继承”了@Component注解接口的注解修饰用户的类,会被Spring中的注解处理器获取(通过getAnonations()).
判定存在@Component注解后,注解处理器会在spring容器框架中,根据用户类的全限定名,通过java的反射机制创建这个用户类的对象,并放到spring容器框架中进行管理。
4.注解处理器(Annotation Processor)
首先来了解下什么是注解处理器,注解处理器是javac的一个工具,它用来在编译时扫描和处理注解(Annotation)。可以自定义注解,并注册到相应的注解处理器,由注解处理器来处理你的注解。
一个注解的注解处理器,以Java代码(或者编译过的字节码)作为输入,生成文件(通常是.java文件)作为输出。这些生成的Java代码是在生成的.java文件中,所以你不能修改已经存在的Java类,例如向已有的类中添加方法。这些生成的Java文件,会同其他普通的手动编写的Java源代码一样被javac编译。
注解处理器实际操作案例
处理运行时注解
Retention的值为RUNTIME时, 注解会保留到运行时, 因此使用反射来解析注解.
使用的注解就是上一步的@TestAnnotation
, 解析示例如下:
public class Demo {
@TestAnnotation("Hello Annotation!")
private String testAnnotation;
public static void main(String[] args) {
try {
// 获取要解析的类
Class cls = Class.forName("myAnnotation.Demo");
// 拿到所有Field
Field[] declaredFields = cls.getDeclaredFields();
for(Field field : declaredFields){
// 获取Field上的注解
TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
if(annotation != null){
// 获取注解值
String value = annotation.value();
System.out.println(value);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
这里只演示了解析成员变量上的注解, 其他类型与此类似.
解析编译时注解
解析编译时注解需要继承AbstractProcessor类, 实现其抽象方法
public boolean process(Set annotations, RoundEnvironment roundEnv)
该方法返回ture表示该注解已经被处理, 后续不会再有其他处理器处理; 返回false表示仍可被其他处理器处理.
// 指定要解析的注解
@SupportedAnnotationTypes("myAnnotation.TestAnnotation")
// 指定JDK版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcesser extends AbstractProcessor {
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
TestAnnotation testAnnotation = element.getAnnotation(TestAnnotation.class);
// do something
}
}
return true;
}
}
上面先大致介绍是什么样子, 接下来说具体实践过程.
Android中使用编译时注解
开发注解库
在IDEA中新建java项目, 并开启maven支持. 如果新建项目的页面没有maven选项, 建好项目后右键项目目录->"Add Framwork Support...", 选择maven.
自定义编译时注解
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface TestAnnotation {
String value() default "Hello Annotation";
}
解析编译时注解
// 支持的注解类型, 此处要填写全类名
@SupportedAnnotationTypes("myannotation.TestAnnotation")
// JDK版本, 我用的是java7
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {
// 类名的前缀后缀
public static final String SUFFIX = "AutoGenerate";
public static final String PREFIX = "My_";
@Override
public boolean process(Set annotations, RoundEnvironment roundEnv) {
for (TypeElement te : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(te)) {
// 准备在gradle的控制台打印信息
Messager messager = processingEnv.getMessager();
// 打印
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.toString());
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getSimpleName());
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + e.getEnclosingElement().toString());
// 获取注解
TestAnnotation annotation = e.getAnnotation(TestAnnotation.class);
// 获取元素名并将其首字母大写
String name = e.getSimpleName().toString();
char c = Character.toUpperCase(name.charAt(0));
name = String.valueOf(c+name.substring(1));
// 包裹注解元素的元素, 也就是其父元素, 比如注解了成员变量或者成员函数, 其上层就是该类
Element enclosingElement = e.getEnclosingElement();
// 获取父元素的全类名, 用来生成包名
String enclosingQualifiedName;
if(enclosingElement instanceof PackageElement){
enclosingQualifiedName = ((PackageElement)enclosingElement).getQualifiedName().toString();
}else {
enclosingQualifiedName = ((TypeElement)enclosingElement).getQualifiedName().toString();
}
try {
// 生成的包名
String genaratePackageName = enclosingQualifiedName.substring(0, enclosingQualifiedName.lastIndexOf('.'));
// 生成的类名
String genarateClassName = PREFIX + enclosingElement.getSimpleName() + SUFFIX;
// 创建Java文件
JavaFileObject f = processingEnv.getFiler().createSourceFile(genarateClassName);
// 在控制台输出文件路径
messager.printMessage(Diagnostic.Kind.NOTE, "Printing: " + f.toUri());
Writer w = f.openWriter();
try {
PrintWriter pw = new PrintWriter(w);
pw.println("package " + genaratePackageName + ";");
pw.println("\npublic class " + genarateClassName + " { ");
pw.println("\n /** 打印值 */");
pw.println(" public static void print" + name + "() {");
pw.println(" // 注解的父元素: " + enclosingElement.toString());
pw.println(" System.out.println(\"代码生成的路径: "+f.toUri()+"\");");
pw.println(" System.out.println(\"注解的元素: "+e.toString()+"\");");
pw.println(" System.out.println(\"注解的值: "+annotation.value()+"\");");
pw.println(" }");
pw.println("}");
pw.flush();
} finally {
w.close();
}
} catch (IOException x) {
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
x.toString());
}
}
}
return true;
}
}
总结以下:只做了两件事,
1.解析注解并获取需要的值
2.使用JavaFileObject
类生成java代码.
注意:上面是使用了JavaIO流完成Java文件的生成,类似StringBuilder
来生成对应的Java代码。这种做法是比较麻烦的,
还有一种更优雅的方式,那就是javapoet。具体这里不在做介绍,可以参考Javapoet技术生成Java代码
向JVM声明解析器,即注册。
如图
在java的同级目录新建resources目录, 新建META-INF/services/javax.annotation.processing.Processor
文件, 文件中填写你自定义的Processor全类名
然后打出jar包以待使用。
Android中使用
使用apt插件
项目根目录gradle中buildscript
的dependencies
添加
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
module目录的gradle中, 添加
apply plugin: 'android-apt'
代码中调用
将之前打出的jar包导入项目中, 在MainActivity中写个测试方法
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
test();
}
@TestAnnotation("hehe")
public void test(){
}
运行一遍项目之后, 代码就会自动生成.
以下是生成的代码, 在路径yourmodule/build/generated/source/apt/debug/yourpackagename
中:
public class My_MainActivityAutoGenerate {
/** 打印值 */
public static void printTest() {
// 注解的父元素: com.example.pan.androidtestdemo.MainActivity
System.out.println("代码生成的路径: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/My_MainActivityAutoGenerate.java");
System.out.println("注解的元素: test()");
System.out.println("注解的值: hehe");
}
}
然后在test方法中调用自动生成的方法
@TestAnnotation("hehe")
public void test(){
My_MainActivityAutoGenerate.printTest();
}
会看到以下打印结果:
代码生成的路径: file:/Users/Pan/AndroidStudioProjects/AndroidTestDemo/app/build/generated/source/apt/debug/com/example/pan/androidtestdemo/MainActivityAutoGenerate.java
注解的元素: test()
注解的值: hehe
关注我的技术公众号,每天都有优质技术文章推送。
微信扫一扫下方二维码即可关注: