APT是一种能够在代码编译时处理注解,并且按照一定的规则生成新的Java代码的技术,假如在新的Java代码中又存在有APT需要处理的注解,APT就会继续执行直到新生成的代码没有要处理的注解为止。目前很多优秀的开源框架比如ButterKnife 、EventBus 和Dagger2 等都采用APT编译时生成代码,取代它们之前的反射实现,提高框架的执行效率。APT和反射生成代码的原理很相似,但它们使用的接口差别很大,而且由于APT是在编译时运行的很多运行时的数据结构都不存在比如数据实体类Student.class是不存在且无法使用的,APT能够使用的就是编译源代码生成的抽象语法树,通过不断地遍历语法树内部的代码结构获取源码中定义的各种方法类等信息。
APT开发接口
在APT看来所有的Java源代码都是由元素Element组成,其中包元素被定义成PackageElement,类和接口被定义成TypeElement类型元素,方法被定义成ExecutableElement可执行元素,属性和普通变量被定义成VariableElement变量元素。不同的元素还会相互嵌套,TypeElement内部会嵌套ExecutableElement和VariableElement,通过TypeElement.getEnclosedElements()就能获取到它所有的孩子元素;ExecutableElement和VariableElement可以通过getEclosingElement()就可以获取包裹它们的元素也就是它们的父元素。
为了能够更好理解Element开发接口,后面的代码表示出了普通的Student.java源代码中的各种元素,需要注意的是AnnotationMirror类型并不是Element接口的子类型,它是一种针对代码注解单独声明的接口类型,AnnotationMirror类型的对象通常都会依附在Element接口类型对象上。
// Student语法树解析
package com.example.mydatabase.apt.entity; // PackageElement
// 删除了其他的getter/setter
@Table(name = "t_student") // AnnotationMirror
public class Student { // TypeElement
@Id // AnnotationMirror
private int id; // VariableElement
private java.lang.String name; // VariableElement
private int age; // VariableElement
public Student() { } // ExecutableElement
public int getId() { // ExecutableElement
return id;
}
}
// 代表源代码中的注解对象
public interface AnnotationMirror { // 没有继承自Element
DeclaredType getAnnotationType();
Map<? extends ExecutableElement, ? extends AnnotationValue> getElementValues();
}
上面的代码展示了Student编译时APT分析中各种元素对象的分布,虽然@Table和Student它们从源代码结构上看它们都处在包元素内部,按道理来说它们应该处于结构上的同一层级,不过@Table并非Element元素类型,它只是依附在Student这个TypeElement元素上的注解对象,下图很好的展示了Student内部各种元素相互之间的包含关系。
Element接口建模的只是Java的源代码结构,比如父子元素关系其实是它们在语法树上的结构关系,并不是语义上的父子继承关系。APT运行在编译时也就无法获取操作的源代码类运行时Class对象,想要获取语义上的父子关系还需要使用TypeMirror接口。在TypeElement类型元素里有一个getSuperClass()的方法可以获取到它语义上的父类型TypeMirror,比如Student类它在语法树结构上的父元素是com.example.mydatabase.apt.entity包元素,但是在Java语义上它的父类是Object类,通过Student的TypeElement.getSuperClass()获取的TypeMirror就代表Object类镜像。TypeMirror类镜像和Class完全是两个概念,TypeMirror只能在APT中代表编译时语义上的类对象,Class则代表运行时真正的类对象。通过getSuperClass()能够获取语义上的父类,想要访问父类的Element可以通过DeclareType.asElement()返回其TypeElement对象,DeclareType是TypeMirror的子类,在子类的源代码定义里使用的就是父类的DeclareType类型。
Element和TypeMirror在哪里能够被获取到呢,APT通常会在需要处理的Java代码元素上添加一些留存性为Retention.CLASS保留在.class文件中的注解,Java编译器命令javac支持-process选项或者在META-INF/services/下注册注解处理器,自定义的注解处理器都会在编译Java源代码的时候被调用。注解处理器通常都继承自AbsProcess抽象类,它的process()方法会传递RoundEnvironment 类型的处理环境对象,通过RoundEnvironment就能够获取到被注解的Element对象,之后按照既定的规则查询Element对象的属性生成需要的Java源代码。
基础配置代码
网络上使用APT生成Dao类的框架有很多,其中Android JetPack中提供了官方的数据库框架Room ,它的接口实现简单,配置容易上手,这里就模仿Room使用方式来实现APT数据库功能封装。Android应用中可以包含多个数据库,每个数据库又包含多个数据库表,每个表都对应一个数据访问对象Dao,为了分清哪些表数据哪个数据库需要在数据库对象上注解它包含的实体类,实体类Dao名称就对应实体类名+Dao。由于APT框架中的Processor注解处理器需要访问标注在源代码上的注解,应用代码也需要使用注解对象,要为注解新建一个Java类型dbannotation包,注解处理器也需要单独创建Java类型的dbprocessor包,应用代码依赖于dbannotation,annotationProcess依赖于dbprocessor,除此之外还需要为SQLite提供运行时库,一些通用的功能就封装在sqlitelib aar包中。
annotationProcessor project(':dbprocessor')
implementation project(':sqlitelib')
implementation project(':dbannotation')
建好注解包和注解处理包后开始在注解包中添加数据库、实体、数据访问对象和数据访问对象中的方法注解。其他Table、Id注解意义与反射节一样,不过存留类型变成了RetentionPolicy.CLASS.
// APT数据库注解
@Target(ElementType.TYPE) // 只能注解类型
@Retention(RetentionPolicy.CLASS) // 保存在.class文件中
public @interface Database { // 数据库注解
Class[] tables(); // 数据库包含的数据库表有哪些
int version(); // 当前数据库的版本号
String name(); // 数据库文件名
}
@Target(ElementType.TYPE) // 注解接口类型
@Retention(RetentionPolicy.CLASS) // 保存在.class文件中
public @interface Dao { // 该接口是数据访问接口
}
@Retention(RetentionPolicy.CLASS) // 保存在.class文件中
@Target(ElementType.METHOD) // 用来注解加载方法,加载方法使用键值做参数
public @interface Load {
}
接着要定义需要使用到的SQLite运行时的基础类,在sqlitelib中只需要一个抽象的AbsDatabase它里面有两个回调函数,onDBCreate()在创建数据库的时候被调用,onDBUpgrade()在数据库被更新的时候调用,开发者可以在它们中增加自己的处理。
public abstract class AbsDatabase {
protected void onDBCreate(SQLiteDatabase db) { }
protected void onDBUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { }
}
准备好注解和基础类后用户可以提供自己的数据库实现和实体类、实体Dao接口的定义,配置工作完成后就可以编写注解处理程序生成需要的代码了。
// 开发者数据库配置代码
@Database(tables = { Student.class, Teacher.class}, version = 1, name = "grade.db")
public abstract class DbManager extends AbsDatabase {
public abstract StudentDao getStudentDao();
public abstract TeacherDao getTeacherDao();
}
@Table
public class Student {
@Id
private int id;
private String name;
...... // 省略其他属性
}
@Dao
public interface StudentDao {
@Load
Student load(int id);
......// 省略其他方法
}
DbManager是用户实现的类,它的Database注解提供了当前数据库中包含Student.class和Teacher.class两个实体类,版本号是1,数据库名为grade.db;它内部还包含了两个Dao访问对象的获取方法。
分析配置代码
现在先创建一个简单的CheckProcessor用来查看DbManager应用代码的Element结构,定义好的CheckProcessor还需要在图2-2的文件中注册。
在继续APT动态代码生成之前再来分析一下前面的配置代码,DbManager继承自AbsDatabase类型,内部有两个生成数据访问对象的接口方法, DBManager的注解@Database里定义了数据名、版本号和需要创建的数据库表实体类。在生成DBManager子类实现时需要先解析出数据库名称和版本号来创建数据库;接着解析出tables里的数据库表实体类,通过解析实体类生成对应的数据库表;最后就需要解析数据访问对象,得到它内部定义的所有方法及方法上定义的SQL字符串或者方法执行类型和方法参数,使用解析出SQL语句和参数名后就能可以直接生成访问数据的实现代码。
在开始真正编写APT解析代码之前,有必要了解一下Element、Type Mirror和AnnotationMorror、AnnotationValue等常用开发接口内部定义的方法。
// APT重要接口简述
// ElementType是enum类型
public interface Element extends AnnotatedConstruct {
TypeMirror asType(); // 获取该元素的TypeMirror真实类型
ElementKind getKind(); // Element类型,比如包、类、接口、方法、变量、构造函数等
Name getSimpleName(); // 元素的简单名称,类名、方法名、变量名等
Element getEnclosingElement(); // 包含该元素的源码结构上的父元素
List<? extends Element> getEnclosedElements();// 元素包含的各种子元素
List<? extends AnnotationMirror> getAnnotationMirrors(); // 获取在元素上的各种注解
<A extends Annotation> A getAnnotation(Class<A> var1); // 直接获取注解实例
// 删减了不重要的接口方法
}
// TypeKind是Enum类型,代表Java源代码语义上的类型
public interface TypeMirror extends AnnotatedConstruct {
TypeKind getKind(); // 类型,int、short等Java基本类型,Object类型,泛型类型等
// 删减了不重要的接口方法
}
// 注解内部的值
public interface AnnotationValue {
Object getValue(); // 返回注解值对象
// 删减了不重要的接口方法
}
上诉代码只列出了最基本的三个接口内部提供的方法,实际上Element有很多子类比如TypeElement和ExecutableElement,TypeElement代表类元素和接口元素,通过TypeElement还可以获取到继承的父类型TypeMirror,实现了的各种接口类型的TypeMirror以及泛型参数类型值,ExecutableElement代表的是方法元素,它还提供各种获取返回参数类型、传入参数类型和抛出异常类型等接口,在实际开发中用到时再详述。
DbManager APT分析
@Database注解APT分析
StudentDao APT分析
上图分析出了DbManager配置类内部的元素结构,@Database注解了DbManager类TypeElement类型元素,DbManager内部又包含了构造函数和获取StudentDao与TeacherDao的三个ExecutableElement可执行方法元素,@Database注解内部定义了三个AnnotationValue类型的结构,每个AnnotationValue对应该注解的一个属性值。
// CheckProcessor检查数据库配置
public class CheckProcessor extends AbstractProcessor {
// Processor支持的注解类型集合
private Set<String> mSet;
// 初始化方法
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
mSet = new HashSet<>();
// 初始化的时候指定仅仅查看Database注解的元素
mSet.add(Database.class.getCanonicalName());
}
// 注解处理方法
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
// 获取被Database注解的元素
Set<? extends Element> database = env.getElementsAnnotatedWith(Database.class);
for (Iterator<? extends Element> i = database.iterator(); i.hasNext();) {
// database集合内部只有一个元素TypeElement
TypeElement element = (TypeElement) i.next();
System.out.println(element); // com.example.mydatabase.apt.DbManager
// 获取DbManager TypeElement包含的所有元素
List<? extends Element> elements = element.getEnclosedElements();
// 包含三个Executable元素,也就是三个方法
// DbManager()
// getStudentDao()
// getTeacherDao()
for (Iterator<? extends Element> eli = elements.iterator(); eli.hasNext(); ) {
Element el = eli.next();
System.out.println(el);
}
// 获取包含DBManager TypeElement的元素,是PackageElement
Element enclosingElement = element.getEnclosingElement();
// com.example.mydatabase.apt
System.out.println(enclosingElement);
// 获取DBManager上面的注解镜像列表,只有一个注解镜像
List<? extends AnnotationMirror> annotationMirrors = element.getAnnotationMirrors();
for (Iterator<? extends AnnotationMirror> mirrorIter = annotationMirrors.iterator(); mirrorIter.hasNext(); ) {
AnnotationMirror annotationMirror = mirrorIter.next();
// 唯一的注解镜像
// @com.example.dbannotation.annotation.Database(
// tables={com.example.mydatabase.apt.entity.Student.class,
// com.example.mydatabase.apt.entity.Teacher.class},
// version=1, name="grade.db")
System.out.println(annotationMirror);
// 打印注解镜像里面的所有属性值
Map<? extends Element, ? extends AnnotationValue> annotationValues =
annotationMirror.getElementValues();
// tables() = {com.example.mydatabase.apt.entity.Student.class,
// com.example.mydatabase.apt.entity.Teacher.class}
// version() = 1
// name() = "grade.db"
for (Map.Entry<? extends Element, ? extends AnnotationValue> entry :
annotationValues.entrySet()) {
System.out.println(entry.getKey() + " = " + entry.getValue());
}
}
// 直接使用getAnnotation()获取Database注解
Database db = element.getAnnotation(Database.class);
System.out.println(db.name()); //打印String类型的属性没问题,输出grade.db
// System.out.println(db.tables()); // 打印.class运行时的对象,抛出异常
}
return true;
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return mSet; // 注解处理器支持的注解类型,只支持Database注解
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7; // 注解处理器支持的最低源代码版本
}
}
上面的代码中定义的CheckProcessor注解处理器中的process()方法就是专门负责处理运行时注解的处理方法,getSupportedAnnotationTypes()返回的集合中包含了本注解处理器会处理的注解元素,从上面的实现可以看出CheckProcessor仅仅访问了被@Database注解的元素类型。AnnotationMirror代表的是Annotation注解的实例,AnnotationMirror里包含AnnotationValue列表,AnnotationValue包含注解的一个属性的键和值。由于APT处理的是编译时源码结构,Student.class是运行时的Class对象在编译时并不存在,直接访问就会抛出异常,编译时的Student.class其实是DeclareType类型,调用DeclareType.asElement()就能获取Student的TypeElement类型元素。
生成Java代码
实体类和DBManager数据库管理类的语法结构都已经表述清楚,Dao接口其实包含的都是Executable方法元素,它内部的语法结构相对简单就不再多做描述。现在考虑实现数据库处理器,由于DbManager包含两个获取Dao对象的抽象方法,想要生成具体的DbManagerImpl类就需要知道实体类Dao的实现类,在处理注解过程中先把所有的Dao类都生成,最后再生成DbManagerImpl类。Database注解的tables属性只指定了实体类型。
// 数据库操作代码自动生成类
public class DbProcessor extends AbstractProcessor {
private Elements mElements; // Element元素类辅助工具
private Types mTypes; // TypeMirror辅助工具
private Filer mFiler; // 源代码写入到文件输出工具
private Messager mMessager; // 打印日志工具
private Set<String> mSet; // DBProcessor支持的所有注解类
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
@Override
public synchronized void init(ProcessingEnvironment env) {
super.init(env);
mElements = env.getElementUtils();
mTypes = env.getTypeUtils();
mFiler = env.getFiler();
mMessager = env.getMessager();
mSet = new HashSet<>();
// 本注解处理器支持Database Table和Dao三个注解
mSet.add(Database.class.getCanonicalName());
mSet.add(Table.class.getCanonicalName());
mSet.add(Dao.class.getCanonicalName());
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment env) {
// 删除判空和错误处理代码
Set<? extends Element> database = env.getElementsAnnotatedWith(Database.class);
Set<? extends Element> tables = env.getElementsAnnotatedWith(Table.class);
Set<? extends Element> daos = env.getElementsAnnotatedWith(Dao.class);
// 遍历Database注解的元素,也就是DbManager类的TypeElement
for (Element element : database) {
if (element instanceof TypeElement) {
TypeElement type = (TypeElement) element;
Database dbAnnotation = type.getAnnotation(Database.class);
String dbName = dbAnnotation.name(); // 获取数据库名
int dbVersion = dbAnnotation.version(); // 数据库版本
// 获取数据库里所有的实体元素
// DBManager所在的包元素
List<TypeElement> tableElements = parseTables(type); PackageElement packageElement = (PackageElement)
type.getEnclosingElement();
// 生成实体TypeElement和Dao接口TypeElement的映射
Map<TypeElement, TypeElement> daoMap =
filterDao(tableElements, daos, packageElement);
// 根据数据库数据和包元素、实体与Dao接口映射生成Dao实现代码
generateDaos(type, dbName, dbVersion, packageElement, daoMap);
}
}
return true;
}
// 其他代码暂时省略
@Override
public Set<String> getSupportedAnnotationTypes() {
return mSet; // 支持Database、Table和Dao三个注解
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_7;
}
}
处理器代码只是整个注解处理的框架部分,首先找到Database注解的元素,接着分析Database注解里的数据库实体类和库名版本等信息,根据实体类TypeElement查找它对应的Dao接口TypeElement,所有数据准备好之后就可以准备生成Dao实现对象。parseTables()方法会从Database的AnnotationMirror对象中分析出tables属性包含的实体类TypeElement。
// 解析数据库表类
private List<TypeElement> parseTables(TypeElement type) {
// 删除了所有的错误处理代码
List<TypeElement> tables = new ArrayList<>();
List<? extends AnnotationMirror> mirrors = type.getAnnotationMirrors();
AnnotationMirror dbAnnotationMirror = mirrors.get(0);
Map<? extends ExecutableElement, ? extends AnnotationValue> map =
dbAnnotationMirror.getElementValues();
for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : map.entrySet()) {
// 从AnnotationValue列表中找到tables属性的值
if (entry.getKey().getSimpleName().contentEquals("tables")) {
AnnotationValue value = entry.getValue();
annotationValues = (List<AnnotationValue>) value.getValue();
break;
}
}
// tables属性值又是一个AnnotationValue列表,遍历列表获取实体类的TypeElement
for (AnnotationValue annotationValue : annotationValues) {
DeclaredType declaredType = (DeclaredType) annotationValue.getValue();
tables.add((TypeElement) declaredType.asElement());
}
// 得到本数据库的所有实体类TypeElement
return tables;
}
filterDao()方法实现比较简单就是比较Dao接口的名字是否和实体类名+Dao一样,一致的话就是该实体类对应的Dao接口。代码2-26会将从@Database中解析出来的实体类性和@Dao解析出来的实体访问对象类型相对应保存到映射类型的daoMap结构中,后面生成实体访问对象代码时需要实体类信息。
// 实体类与实体访问对象接口对应解析
private Map<TypeElement, TypeElement> filterDao(List<TypeElement> tableElements,
Set<? extends Element> daos, PackageElement packageElement) {
Map<TypeElement, TypeElement> daoMap = new HashMap<>();
for (TypeElement typeElement : tableElements) {
for (Element element : daos) {
if (element instanceof TypeElement) {
TypeElement daoElement = (TypeElement) element;
String qulifiedName = daoElement.getQualifiedName().toString();
String pkgQualifiedName = packageElement.getQualifiedName().toString();
if (qulifiedName .startsWith(pkgQualifiedName)) {
if (daoElement.getSimpleName()
.contentEquals(typeElement.getSimpleName().toString() + "Dao")) {
daoMap.put(typeElement, daoElement);
}
}
}
}
}
return daoMap;
}
下面代码中的generateDaos()的实现其实就是遍历前面的daoMap,从映射中取出实体类TypeElement和Dao接口的TypeElement,最终生成Dao对象,所有Dao生成完之后最后生成DBManagerImpl源代码。
// 生成Dao和Database源代码
private void generateDaos(TypeElement database, String dbName, int dbVersion, PackageElement packageElement, Map<TypeElement, TypeElement> daoMap) {
Set<String> generateDaos = new HashSet<>();
for (Map.Entry<TypeElement, TypeElement> entry : daoMap.entrySet()) {
TypeElement table = entry.getKey();
TypeElement dao = entry.getValue();
String qualifiedDaoName = generateDao(packageElement, table, dao);
generateDaos.add(qualifiedDaoName);
}
generateDatabase(database, dbName, dbVersion, daoMap.keySet(), generateDaos);
}
generateDao()方法其实就是字符串拼接,生成Dao对象的类声明很简单就和写Java代码一样,最重要的是Dao中的方法实现,这里因为篇幅的缘故只简单介绍load()加载方法的实现逻辑,代码中拼接字符串的操作都一律使用注释代替。
// 生成Dao源代码
private String generateDao(PackageElement packageElement, TypeElement table,
TypeElement dao) {
// 字符串拼接生成Dao源代码开始部分
// package com.example.mydatabase.apt.impl;
// public class StudentDaoImpl implements com.example.mydatabase.apt.dao.StudentDao {
// protected android.database.sqlite.SQLiteDatabase mDb;
// public StudentDaoImpl(android.database.sqlite.SQLiteDatabase db) {
// this.mDb = db;
// }
// 获取实体对象里面所有的VariableElement也就是实体类属性
List<? extends Element> elements = table.getEnclosedElements();
List<VariableElement> columns = new ArrayList<>();
for (Element variable : elements) {
if (variable instanceof VariableElement) {
columns.add((VariableElement) variable);
}
}
// 遍历Dao接口中的所有方法
for (Element element : dao.getEnclosedElements()) {
if (element instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) element;
// 获取方法上的注解
Load load = method.getAnnotation(Load.class);
if (load != null) {
// 获取方法的所有参数
List<? extends VariableElement> params = method.getParameters();
if (!CollectionUtils.isEmpty(params) && params.size() == 1) {
// 将方法参数加入到Java方法中
// public java.util.List<com.example.mydatabase.apt.entity.Student>
// query(int age,int id) {
// android.database.Cursor cursor = mDb.rawQuery(
// 根据实体对象生成加载SQL语句
String loadSql = generateLoadSql(table, columns);
// 生成通过loadSQL加载实体类的代码
// new String[] { String.valueOf(age),String.valueOf(id)});
// java.util.List<com.example.mydatabase.apt.entity.Student> list =
// new java.util.ArrayList<>();
// while (cursor.moveToNext()) {
// com.example.mydatabase.apt.entity.Student obj =
// newStudent(cursor);
// list.add(obj);
// }
// return list;
// }
}
continue;
}
}
}
// 根据实体类和属性列表生成创建新对象并赋值的源代码
generateNewMethod(builder, table, columns);
builder.append("}");
String fullName = packageName + "." + className;
// 将源代码写到XXXDaoImpl.java文件中
writeToFile(fullName, builder.toString());
return fullName;
}
// 生成数据库访问方法的SQL语句
private String generateLoadSql(TypeElement table, List<VariableElement> columns) {
StringBuilder builder = new StringBuilder();
Table t = table.getAnnotation(Table.class); // 获取表信息
// 获取表名
String tableName = TextUtils.isEmpty(t.name()) ?
table.getSimpleName().toString() : t.name();
builder.append("SELECT * FROM ").append(tableName).append(" WHERE ");
for (VariableElement element : columns) {
Id id = element.getAnnotation(Id.class);
String name = element.getSimpleName().toString();
if (id != null) { // 根据Student类字段生成加载SQL语句
name = TextUtils.isEmpty(id.value()) ? name : id.value();
builder.append(name).append("=?;");
break;
}
}
return builder.toString();
}
// 生成实体对象并且设置新对象的所有属性值
private void generateNewMethod(StringBuilder builder, TypeElement table,
List<VariableElement> columns) {
// 生成创建新对象并设置值的方法newStudent()
// private com.example.mydatabase.apt.entity.Student
// newStudent(android.database.Cursor cursor) {
// com.example.mydatabase.apt.entity.Student obj = new
// com.example.mydatabase.apt.entity.Student();
for (VariableElement variableElement : columns) {
TypeKind typeKind = variableElement.asType().getKind();
if (typeKind == TypeKind.INT || typeKind == TypeKind.SHORT ||
typeKind == TypeKind.BYTE) {
String type = typeKind == TypeKind.INT ? "int" :
typeKind == TypeKind.SHORT ? "short" : "byte";
builder.append("int ")
.append(variableElement.getSimpleName().toString())
.append(" = cursor.getInt");
// 生成赋值操作
// int id = cursor.getInt(cursor.getColumnIndex("id"));
// obj.setId((int)id);
}
// 省略其他类型解析
}
builder.append("return obj;").append(LINE_SEPARATOR); // 返回对象
builder.append("}").append(LINE_SEPARATOR); // 增加最后一个花括号
}
上诉代码中实现过程其实就是根据实体类属性生成SQL语句,调用SQLiteDatabase接口执行查询,再通过实体类名和解析出来的属性生成新的对象返回。代码生成其实就是拼接字符串操作,实现的源代码里面的逻辑跟简单封装非常类似,只不过这里使用了APT动态生成源代码。APT生成的源码被放在图2-8所示的位置,生成的源代码和开发者自己手写的Java代码一样都会参与应用的编译生成.class文件,开发者就能像使用普通类一样调用生成类的方法。