APT在编译期间生成代码
Java注解处理器使用详解
注解处理器运行是不会被打包apk,是在运行前生成代码否则会发生jar冲突,手机系统已经存在
虚处理器AbstractProcessor
-
init(ProcessingEnvironment env)
:
每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment
参数。ProcessingEnviromen
t提供很多有用的工具类Elements
,Types
和Filer
。后面我们将看到详细的内容。 -
process(Set<? extends TypeElement> annotations, RoundEnvironment env)
:
这相当于每个处理器的主函数main()
。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment
,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。 -
getSupportedAnnotationTypes()
:
这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。 -
getSupportedSourceVersion()
:
用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()
。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6
。我推荐你使用前者。
利用JavaPoet创建类
// `JavaFile` 代表 Java 文件
JavaFile javaFile = JavaFile.builder("com.walfud.howtojavapoet",
// TypeSpec 代表一个类
TypeSpec.classBuilder("Clazz")
// 给类添加一个属性
.addField(FieldSpec.builder(int.class, "mField", Modifier.PRIVATE)
.build())
// 给类添加一个方法
.addMethod(MethodSpec.methodBuilder("method")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addStatement("System.out.println(str)")
.build())
.build())
.build();
@AutoService(Process.class)
public class MvpProcessor extends AbstractProcessor {
private Elements mElementsUtils;
private Messager mMessager;
private Filer mFiler;
/**
* @param processingEnvironment
* 该方法主要用于一些初始化的操作,
* 通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类。
* 其内部各方法解释如下:
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// * 返回用来在元素上进行操作的某些实用工具方法的实现。
// * Elements是一个工具类,可以处理相关Element(
// * 包括ExecutableElement, PackageElement, TypeElement, TypeParameterElement, VariableElement)
mElementsUtils = processingEnvironment.getElementUtils();//元素操作工具类
// * 返回用来报告错误、警报和其他通知的 Messager。
mMessager = processingEnvironment.getMessager();// 日志工具类
// * 用来创建新源、类或辅助文件的 Filer。
mFiler = processingEnvironment.getFiler();
//
// 返回用来在类型上进行操作的某些实用工具方法的实现。
// Types getTypeUtils();
//
// // 返回任何生成的源和类文件应该符合的源版本。
// SourceVersion getSourceVersion();
//
// // 返回当前语言环境;如果没有有效的语言环境,则返回 null。
// Locale getLocale();
//
// // 返回传递给注释处理工具的特定于 processor 的选项
// Map<String, String> getOptions();
}
/**
* @return
* 返回此 Processor 支持的注释类型的名称。
* 结果元素可能是某一受支持注释类型的规范(完全限定)名称。它也可能是 ” name.” 形式的名称
* ,表示所有以 ” name.” 开头的规范名称的注释类型集合。最后,自身表示所有注释类型的集合,
* 包括空集。注意,Processor 不应声明 “*”,除非它实际处理了所有文件;
* 声明不必要的注释可能导致在某些环境中的性能下降。
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new HashSet<>();
// 1、getCanonicalName() 是获取所传类从java语言规范定义的格式输出。
// 2、getName() 是返回实体类型名称
// 3、getSimpleName() 返回从源代码中返回实例的名称。
types.add(MvpEntity.class.getCanonicalName());
return types;
}
// 返回此注释 Processor 支持的最新的源版本,该方法可以通过注解@SupportedSourceVersion指定。
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
/**
* @param set
* @param roundEnvironment
* @return
* 注解处理器的核心方法,处理具体的注解。主要功能基本可以理解为两个
* 获取同一个类中的所有指定注解修饰的Element;
* set参数,存放的是支持的注解类型
* RoundEnvironment参数,可以通过遍历获取代码中所有通过指定注解(例如在ButterKnife中主要就是@BindeView等)
* 修饰的Element对象。通过Element对象可以获取字段名称,字段类型以及注解元素的值。
* 创建Java文件;
* 将同一个类中通过指定注解修饰的所有Element在同一个Java文件中实现初始化,
* 这样做的目的是让在最终依赖注入时便于操作。
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 返回一个注解集合
// 即element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。
// element是java-apt(编译时注解处理器)技术的基础,因此如果要编写此类框架,熟悉element是必须的。
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MvpEntity.class);
for (Element element : elementsAnnotatedWith) {
// 检查element类型
if (!checkAnnotationValid(element, MvpEntity.class)) {
return false;
}
TypeElement typeElement = (TypeElement) element;
// 得到该类的类名
String className = typeElement.getSimpleName().toString();
// 得到该类包名
String packageName = mElementsUtils.getPackageOf(element).getQualifiedName().toString();
genneratorClass(className, packageName);
}
return false;
}
//利用JavaPoet创建类
//javaPoet是一款可以自动生成Java文件的第三方依赖
private void genneratorClass(String className, String packageName) {
// 获得父类
ParameterizedTypeName superClass = ParameterizedTypeName.get(ClassName.get(packageName, className), TypeVariableName.get("D"));
// 利用JavaPoet创建类
TypeSpec.Builder typeVariable = TypeSpec.classBuilder("ProxyEntity")
.addModifiers(Modifier.PUBLIC)//创建修饰符
.superclass(superClass)//创建父类
.addTypeVariable(TypeVariableName.get("D"));//添加范型
// 创建该类
TypeSpec build = typeVariable.build();
// 创建该Java文件
JavaFile javaFile = JavaFile.builder(packageName, build).build();
try {
// 写入该类
javaFile.writeTo(mFiler);
} catch (IOException e) {
e.printStackTrace();
}
}
// 检查字段
private boolean checkAnnotationValid(Element annotatedElement, Class clazz) {
// 检查注解是不是类
if (annotatedElement.getKind() != ElementKind.CLASS) {
return false;
}
// 检查是不是private
if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) {
return false;
}
return true;
}
}
你看到在代码的第一行是@AutoService(Processor.class)
,这是什么?这是一个其他注解处理器中引入的注解。AutoService
注解处理器是Google开发的,用来生成META-INF/services/javax.annotation.processing.Processor
文件的。是的,你没有看错,我们可以在注解处理器中使用注解。非常方便,难道不是么?在getSupportedAnnotationTypes()
中,我们指定本处理器将处理@MvpEntity
注解。
Elements
和TypeMirrors
在init()
中我们获得如下引用:
Elements
:一个用来处理Element的工具类(后面将做详细说明);
Types
:一个用来处理TypeMirror的工具类(后面将做详细说明);
Filer
:正如这个名字所示,使用Filer你可以创建文件。
在注解处理过程中,我们扫描所有的Java源文件。源代码的每一个部分都是一个特定类型的Element。换句话说:Element代表程序的元素,例如包、类或者方法。每个Element代表一个静态的、语言级别的构件。在下面的例子中,我们通过注释来说明这个:
我们来一步一步实现process()方法。首先,我们从搜索被注解了@MvpEntity的类开始:
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 返回一个注解集合
// 即element是代表程序的一个元素,这个元素可以是:包、类/接口、属性变量、方法/方法形参、泛型参数。
// element是java-apt(编译时注解处理器)技术的基础,因此如果要编写此类框架,熟悉element是必须的。
Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(MvpEntity.class);
// 遍历所有被注解了@Factory的元素
for (Element element : elementsAnnotatedWith) {
// 检查element类型
if (!checkAnnotationValid(element, MvpEntity.class)) {
return false;
}
TypeElement typeElement = (TypeElement) element;
// 得到该类的类名
String className = typeElement.getSimpleName().toString();
// 得到该类包名
String packageName = mElementsUtils.getPackageOf(element).getQualifiedName().toString();
genneratorClass(className, packageName);
}
return false;
}
这里并没有什么高深的技术。roundEnv.getElementsAnnotatedWith(Factory.class))
返回所有被注解了@Factory
的元素的列表。你可能已经注意到,我们并没有说“所有被注解了@MvpEntity
y的类的列表”,因为它真的是返回Element的列表。请记住:Element
可以是类、方法、变量等。所以,接下来,我们必须检查这些Element
是否是一个类:
// 检查字段
private boolean checkAnnotationValid(Element annotatedElement, Class clazz) {
// 检查注解是不是类
if (annotatedElement.getKind() != ElementKind.CLASS) {
return false;
}
// 检查是不是private
if (annotatedElement.getModifiers().contains(Modifier.PRIVATE)) {
return false;
}
return true;
}
为什么要这么做?我们要确保只有class
元素被我们的处理器处理。前面我们已经知道到类是用TypeElement
表示。我们为什么不这样判断呢if (! (annotatedElement instanceof TypeElement) )?这是错误的,因为接口(interface)类型也是TypeElement。所以在注解处理器中,我们要避免使用instanceof,而是配合TypeMirror使用EmentKind或者TypeKind