注解-第五篇
程序员文章站
2022-03-14 19:02:02
...
编译时注解
上篇文章说了一下编译时注解的用法和使用步骤,并写了一个Demo编译时生成本地文件,这篇文章会通过编译时注解生成一个Java类看一下编译时注解在安卓中的具体使用.
1、新建Android工程名为 AnnotationPractice,完成后,新建一个Module选择Java Library 名为 testlib.
2、在Java工程中定义注解类GenerateCode.
@Documented()
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE})
public @interface GenerateCode {
}
3、在Java工程中定义注解处理器GenerateProcessor
public class GenerateProcessor extends AbstractProcessor {
private Messager messager; // 输入日志
private Filer filer; // 生成文件
private Elements elementsUtils; // Element工具
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(GenerateCode.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
elementsUtils = processingEnvironment.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);
if (elements.iterator().hasNext()) {
Element element = elements.iterator().next(); // 获取set集合的第一个元素
if (ElementKind.CLASS == element.getKind()) {
TypeElement typeElement = (TypeElement) element;
final String pkgName = getPackageName(typeElement); // 获取包名
messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义
final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名
String code = jointCode(pkgName, clsName);
Writer writer = null;
try {
JavaFileObject file = filer.createSourceFile(pkgName + "." + clsName);
writer = file.openWriter();
writer.write(code);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null)
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
return true;
}
/**
* 拼接代码
*
* @param pkgName 包名
* @param clsName 类名
* @return 拼接效果下面这样
*
package pkgName;
public class clsName {
public void print() {
System.out.println("Hello");
}
}
*/
private String jointCode(String pkgName, String clsName) {
StringBuilder builder = new StringBuilder();
builder.append("package ");
builder.append(pkgName);
builder.append(";");
builder.append("\n\n");
builder.append("public class ");
builder.append(clsName);
builder.append(" {");
builder.append("\n");
builder.append("\tpublic void print() {");
builder.append("\n");
builder.append("\t\tSystem.out.println(\"Hello\");");
builder.append("\n");
builder.append("\t}");
builder.append("\n");
builder.append("}");
return builder.toString();
}
private String getPackageName(TypeElement type) {
return elementsUtils.getPackageOf(type).getQualifiedName().toString();
}
}
4、在main下新建resources文件夹,在resources下新建META-INF文件夹,在META-INF下新建services文件夹,在services下新建文件javax.annotation.processing.Processor,在javax.annotation.processing.Processor文件中写如文件注解器全限定名
com.example.bill.testlib.GenerateProcessor
5、编译. 如果配置好gradle环境变量,在Java Module根目录下,即testlib下使用 gradle build 命令编译,编译成功后会在testlib的build下生成jar包(AnnotationPractice/testlib/build/libs/testlib.jar).
6、在app中使用
- 将编译好的testlib.jar拷贝到app的libs下,在app的build.gradle下dependencies中引入jar包
annotationProcessor files('libs/testlib.jar')
- 在app中使用注解,在MainActivity上使用注解GenerateCode
package com.bill.annotationpractice;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import com.example.bill.testlib.GenerateCode;
@GenerateCode
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
7、编译. 在app目录下使用 gradle build 命令编译,编译成功后会在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成我们编写的MainActivityGA就可以在app中使用了.生成的类如下
package com.bill.annotationpractice;
public class MainActivity$GA {
public void print() {
System.out.println("Hello");
}
}
eg:AnnotationPractice-branch4
上面代码通过编译时生成了Java文件,但是需要手动拼接生成的代码和注解处理器的配置信息.下面介绍两个开源库开简化这两步,Google和Square开源的两个库分别用来生成注解处理器的配置信息和生成Java类的
具体使用如下
- 在testlib的build.gradle中的dependencies下引入
dependencies {
compile 'com.google.auto.service:auto-service:1.0-rc2'
compile 'com.squareup:javapoet:1.7.0'
}
注解处理器类上使用@AutoService(Processor.class),他是Google提供的作用是生成注解处理器的配置信息,代替上面的第4步的
在注解处理器中使用javapoet中的api生成代码
MethodSpec methodSpec = MethodSpec.methodBuilder("print")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL) // 方法修饰符
.returns(void.class) // 方法返回值
.addStatement("$T.out.println($S)", // 参数 System.class, "Hello") // 内容
.build();
TypeSpec typeSpec = TypeSpec.classBuilder(clsName) // 类名
.addModifiers(Modifier.PUBLIC) // 类修饰符
.addMethod(methodSpec) //类中的方法
.build();
JavaFile javaFile = JavaFile.builder(pkgName, typeSpec) // 包名和类
.build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
下面通过这两个库来替换上面的Demo
上面第1步和第2步不变,将第3步和第4步去掉,注解处理器改为下面
@AutoService(Processor.class)
public class GenerateProcessor extends AbstractProcessor {
private Messager messager; // 输入日志
private Filer filer; // 生成文件
private Elements elementsUtils; // Element工具
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(GenerateCode.class.getCanonicalName());
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
elementsUtils = processingEnvironment.getElementUtils();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(GenerateCode.class);
if (elements.iterator().hasNext()) {
Element element = elements.iterator().next(); // 获取set集合的第一个元素
if (ElementKind.CLASS == element.getKind()) {
TypeElement typeElement = (TypeElement) element;
final String pkgName = getPackageName(typeElement); // 获取包名
messager.printMessage(Diagnostic.Kind.NOTE, "pkgName:" + pkgName); // 控制台输入的,没有实质意义
final String clsName = typeElement.getSimpleName().toString() + "$GA"; // 获取简单类名
MethodSpec methodSpec = MethodSpec.methodBuilder("print")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addStatement("$T.out.println($S)", System.class, "Hello")
.build();
TypeSpec typeSpec = TypeSpec.classBuilder(clsName)
.addModifiers(Modifier.PUBLIC)
.addMethod(methodSpec)
.build();
JavaFile javaFile = JavaFile.builder(pkgName, typeSpec)
.build();
try {
javaFile.writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
}
return true;
}
private String getPackageName(TypeElement type) {
return elementsUtils.getPackageOf(type).getQualifiedName().toString();
}
}
还是上面第5步编译生成jar包,上面第6步在app中引入使用,第7步中编译,然后会报下面错误,就是找不到javapoet包,因为上面编译生成jar并没有将javapoet的代码打到包里,所以找不到
下面通过引用module的方式引入testlib,在app的build.gradle的dependencies中加入
implementation project(path: ':testlib')
annotationProcessor project(path: ':testlib')
然后在app下编译报错
按照错误提示在app的build.gradle的android下加入下面代码
lintOptions {
abortOnError false
}
编译成功,在app/build/generated/source/apt/debug/com.bill.annotationpractice下生成MainActivity$GA类
package com.bill.annotationpractice;
public class MainActivity$GA {
public void print() {
System.out.println("Hello");
}
}