「Android」通过注解自动生成类文件:APT实战(AbstractProcessor)
文/毛毛
欠了自己好几篇文章还没开始动笔。。。
今天讲点技术干货吧!
最近在做一个自动生成代码的架构,这两天调研了一下APT自动生成代码的流程,动手写了个小demo。
demo 内容:通过获取注解内容来生成新类,再通过调用新类的方法来获取注解的内容,并展示出来。
本文作为总结文,讲解demo的创建过程以及遇到的问题解决。如有描述不详之处,或是遇到了新的问题,欢迎留言探讨。
一、新建工程
创建一个普通的Android工程。
二、新建AbstractProcessor类的实现类。
@SupportedAnnotationTypes("com.autotestdemo.maomao.javalib.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment){
return false;
}
}
坑一:首先你把我这代码拷过去你会发现导不了包!根本找不到AbstractProcessor类。
原因是AbstractProcessor不在Android SDK里面!
所以我们要建【java工程】
但是我们最终要放在app里面运行的,怎么办?
那我们需要建一个java library的module来做为你主工程的引用工程,专门存放AbstractProcessor实例的相关内容。
建好library之后,需要在主工程引用它:
上面的javalib是我新建的java工程,app是我的主工程代码。
我们要在app里的build.gradle文件里添加对javalib的引用:
dependencies {
implementation project(':javalib') // 添加依赖模块
}
三、添加注解
要实现通过获取注解内容来生成新类,所以首先要有个注解。
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface VInjector {
int id();
String name();
String text();
}
@Retention(RetentionPolicy.CLASS)指定了该注解是编译时注解,即程序编译时就能获取到所有该注解的内容。 若指定的是RetentionPolicy.RUNTIME就表示是运行时注解。
@Target(ElementType.TYPE)指定了该注解是作用在类上面的,而不是属性上。
然后指定了一个int类型和两个String类型的接收字段。
用法:
@VInjector(id=1,name="Maomao",text="这是动态代码生成的")
public class MainActivity extends AppCompatActivity {
四、实现AbstractProcessor实例
这是最重要的一步:代码实现
首先看看代码:
@SupportedAnnotationTypes("com.autotestdemo.maomao.javalib.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {
private Filer mFiler;
private Messager mMessager;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
//初始化我们需要的基础工具
mFiler = processingEnv.getFiler();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 遍历所有注解元素
for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(VInjector.class)) {
analysisAnnotated(annotatedElement);
}
return false;
}
private static final String SUFFIX = "AutoClass";
/**
* 生成java文件
* @param classElement 注解
*/
private void analysisAnnotated(Element classElement) {
VInjector annotation = classElement.getAnnotation(VInjector.class);
int id = annotation.id();
String name = annotation.name();
String text = annotation.text();
String newClassName = name + SUFFIX;
StringBuilder builder = new StringBuilder()
.append("package com.autotestdemo.maomao.autotestdemo.auto;nn")
.append("public class ")
.append(newClassName)
.append(" {nn") // open class
.append("tpublic String getMessage() {n") // open method
.append("ttreturn "");
// this is appending to the return statement
builder.append(id).append(text).append(newClassName).append(" !\n");
builder.append("";n") // end returne
.append("t}n") // close method
.append("}n"); // close class
try { // write the file
JavaFileObject source = mFiler.createSourceFile("com.autotestdemo.maomao.autotestdemo.auto." + newClassName);
Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// Note: calling e.printStackTrace() will print IO errors
// that occur from the file already existing after its first run, this is normal
}
}
上面代码分为两部分。
第一部分:获取注解
首先我们看VInjectProcessor上面的两个注解:
@SupportedAnnotationTypes("com.autotestdemo.maomao.javalib.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {
@SupportedAnnotationTypes()是指定哪些注解会由该类处理,里面放注解的全包名路径。 @SupportedSourceVersion()是指定编译器版本
我们再来看看process方法:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 遍历所有注解元素
for (Element annotatedElement : roundEnvironment.getElementsAnnotatedWith(VInjector.class)) {
analysisAnnotated(annotatedElement);
}
return false;
}
process方法体是获取注解内容的唯一途径。
这里面包含了所有符合条件的注解(在@SupportedAnnotationTypes()里指定的),因此我们需要循环取出当个注解实例。
roundEnvironment.getElementsAnnotatedWith(VInjector.class)是获取所有的VInjector注解集合。
第二部分:生成java文件
analysisAnnotated()方法是用于获取到注解内容之后生成与内容相关的java文件。
private static final String SUFFIX = "AutoClass";
/**
* 生成java文件
* @param classElement 注解
*/
private void analysisAnnotated(Element classElement) {
VInjector annotation = classElement.getAnnotation(VInjector.class);
int id = annotation.id();
String name = annotation.name();
String text = annotation.text();
String newClassName = name + SUFFIX;
StringBuilder builder = new StringBuilder()
.append("package com.autotestdemo.maomao.autotestdemo.auto;nn")
.append("public class ")
.append(newClassName)
.append(" {nn") // open class
.append("tpublic String getMessage() {n") // open method
.append("ttreturn "");
// this is appending to the return statement
builder.append(id).append(text).append(newClassName).append(" !\n");
builder.append("";n") // end returne
.append("t}n") // close method
.append("}n"); // close class
try { // write the file
JavaFileObject source = mFiler.createSourceFile("com.autotestdemo.maomao.autotestdemo.auto." + newClassName);
Writer writer = source.openWriter();
writer.write(builder.toString());
writer.flush();
writer.close();
} catch (IOException e) {
// Note: calling e.printStackTrace() will print IO errors
// that occur from the file already existing after its first run, this is normal
}
}
代码大致内容:拿到注解里面的所有内容,生成一个输出所有内容的类。
五、使用Processor
VInjectProcessor类实现好以后,我们怎么使用它?系统如何知道运行它里面的代码?
注解处理器类编写完后,还需要创建一个 java META_INF 文件来告诉系统具有注解处理功能。Java 代码在编译的时候,系统编译器会查找所有的 META_INF 中的注册的注解处理器来处理注解。
在项目中创建如下目录: src/main/resources/META_INF/services
在main目录下创建如下目录和文件:
resources
- META-INF
- services
- javax.annotation.processing.Processor
在 services 目录下面创建一个名字为 “javax.annotation.processing.Processor” 的文本文件,Processor内容如下:
com.autotestdemo.maomao.javalib.VInjectProcessor # 指定处理器全类名
由于我们的VInjectProcessor是在子工程里面,因此我们的目录也需在子工程里面:
六、编译
做完以上步骤,编译工程之后,就可以生出新的类,生成好的类长这样:
package com.autotestdemo.maomao.autotestdemo.auto;
public class MaomaoAutoClass {
public String getMessage() {
return "1这是动态代码生成的MaomaoAutoClass !n";
}
}
该类需要在app目录下的build目录里找,路径如下:
这里有个坑:如果你编译之后,source文件夹下面怎么也找不到apt文件夹,或者报以下错误:
Annotation processors must be explicitly declared now. The following dependencies on the compile classpath are found to contain annotation processor. Please add them to the annotationProcessor configuration.
- javalib.jar (project :javalib)
Alternatively, set android.defaultConfig.javaCompileOptions.annotationProcessorOptions.includeCompileClasspath = true to continue with previous behavior. Note that this option is deprecated and will be removed in the future.
这时候你需要在app下的build.gradle里加入如下引用:
android {
defaultConfig {
//解决多包依赖显示使用注解
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
}
}
七、调用生成的代码
编译成功以后,我们就能直接访问生成的类。
@VInjector(id=1,name="Maomao",text="这是动态代码生成的")
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.text);
tv.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
MaomaoAutoClass auto = new MaomaoAutoClass();
tv.setText(auto.getMessage());
}
});
}
}
我把类名的前缀指定为Maomao,因此生成的类叫MaomaoAutoClass。
此时我们可以访问MaomaoAutoClass类并调用里面的方法。从方法获取的字符串我给它替换掉TextView原有的字符串。
至此,该功能讲解全部完毕。
效果图:
【参考链接】 https://www.jianshu.com/p/003be1b75e28
https://www.jianshu.com/p/07ef8ba80562
https://blog.csdn.net/feirose/article/details/68486790
https://blog.csdn.net/keep_holding_on/article/details/76188657
(2018.12.28 23:58)
- 浅谈 Scala 中下划线的用途
- Java 多线程之 Runnable VS Thread 及其资源共享问题
- 块RAM的Verilog HDL调用
- 玩转千位分隔符输出
- DCM 模块的Verilog HDL 调用
- Python RPC 远程调用脚本之 RPyC 实践
- CRC16 编码器的Verilog HDL 实现
- macOS 0-day漏洞详情披露,可被利用完全接管系统
- SPI 接口协议的Verilog HDL 实现
- 玩转 Nginx 之:使用 Lua 扩展 Nginx 功能
- 键盘防抖
- 按键扫描接口的Verilog HDL 实现
- 病毒分析 | 一只“蜗牛”偷梁换柱,靠锁主页进行牟利
- 移位寄存器的工作原理
- java教程
- Java快速入门
- Java 开发环境配置
- Java基本语法
- Java 对象和类
- Java 基本数据类型
- Java 变量类型
- Java 修饰符
- Java 运算符
- Java 循环结构
- Java 分支结构
- Java Number类
- Java Character类
- Java String类
- Java StringBuffer和StringBuilder类
- Java 数组
- Java 日期时间
- Java 正则表达式
- Java 方法
- Java 流(Stream)、文件(File)和IO
- Java 异常处理
- Java 继承
- Java 重写(Override)与重载(Overload)
- Java 多态
- Java 抽象类
- Java 封装
- Java 接口
- Java 包(package)
- Java 数据结构
- Java 集合框架
- Java 泛型
- Java 序列化
- Java 网络编程
- Java 发送邮件
- Java 多线程编程
- Java Applet基础
- Java 文档注释
- LeetCode 35. 搜索插入位置
- 【面试系列】反射+动态代理,你为何老是搞不懂?
- 我不知道还可以用 JS 做的 6 件事
- 【服务网格架构】Envoy架构概览(7):断路,全局限速和TLS
- 你的消息队列如何保证消息不丢失,且只被消费一次,这篇就教会你
- 【服务网格架构】Envoy架构概览(9):访问日志,MongoDB,DynamoDB,Redis
- Redis Cluster 原理分析
- Ceph介绍及原理架构分享
- 分布式存储Ceph之PG状态详解
- JS中的事件循环机制与宏队列、微队列笔记
- Redis 哨兵机制以及底层原理深入解析,这次终于搞清楚了
- SQL 找出分组中具有极值的行
- 接入层Nginx架构及模块介绍分享
- 【问题修复】mds0: Metadata damage detected
- 【服务网格架构】Envoy架构概览(6):异常检测