本文同步发布在CSDN,未经本人容许不得转载html
上篇文章咱们使用注解+反射实现了一个仿ButterKnife功能的示例。考虑到反射是在运行时完成的,多少会影响程序性能。所以,ButterKnife自己并不是基于注解+反射来实现的,而是用APT技术在编译时处理的。APT什么呢?接下来一块儿来看。java
1.什么是APT? APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT能够用来在编译时扫描和处理注解。经过APT能够获取到注解和被注解对象的相关信息,在拿到这些信息后咱们能够根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提升了程序性能。APT的核心是AbstractProcessor类,关于AbstractProcessor类后面会作详细说明。git
2.哪里用到了APT?github
APT技术被普遍的运用在Java框架中,包括Android项以及Java后台项目,除了上面咱们提到的ButterKnife以外,像EventBus 、Dagger2以及阿里的ARouter路由框架等都运用到APT技术,所以要想了解以、探究这些第三方框架的实现原理,APT就是咱们必需要掌握的。bash
3.如何在Android Studio中构建一个APT项目?app
APT项目须要由至少两个Java Library模块组成,不知道什么是Java Library?不要紧,手把手来叫你如何建立一个Java Library。 框架
1.首先须要一个Annotation模块,这个用来存放自定义的注解。ide
2.另外须要一个Compiler模块,这个模块依赖Annotation模块。工具
3.项目的App模块和其它的业务模块都须要依赖Annotation模块,同时须要经过annotationProcessor依赖Compiler模块。 app模块的gradle中依赖关系以下:性能
implementation project(':annotation')
annotationProcessor project(':factory-compiler')
复制代码
APT项目的模块的结构图以下所示:
为何要强调上述两个模块必定要是Java Library?若是建立Android Library模块你会发现不能找到AbstractProcessor这个类,这是由于Android平台是基于OpenJDK的,而OpenJDK中不包含APT的相关代码。所以,在使用APT时,必须在Java Library中进行。
在学习Java基础的时候想必你们都写过简单工厂模式的例子,回想一下什么是简单工厂模式。接下来引入一个工厂模式的例子,首先定义一个形状的接口IShape,并为其添加 draw()方法:
public interface IShape {
void draw();
}
复制代码
接下来定义几个形状实现IShape接口,并重写draw()方法:
public class Rectangle implements IShape {
@Override
public void draw() {
System.out.println("Draw a Rectangle");
}
}
public class Triangle implements IShape {
@Override
public void draw() {
System.out.println("Draw a Triangle");
}
}
public class Circle implements IShape {
@Override
public void draw() {
System.out.println("Draw a circle");
}
}
复制代码
接下来咱们须要一个工厂类,这个类接收一个参数,根据咱们传入的参数建立出对应的形状,代码以下:
public class ShapeFactory {
public Shape create(String id) {
if (id == null) {
throw new IllegalArgumentException("id is null!");
}
if ("Circle".equals(id)) {
return new Circle();
}
if ("Rectangle".equals(id)) {
return new Rectangle();
}
if ("Triangle".equals(id)) {
return new Triangle();
}
throw new IllegalArgumentException("Unknown id = " + id);
}
}
复制代码
以上就是一个简单工厂模式的示例代码,想必你们都可以理解。
那么,如今问题来了,在项目开发过程当中,咱们随时可能会添加一个新的形状。此时就不得不修改工厂类来适配新添加的形状了。试想一下,每添加一个形状类都须要咱们手动去更新Factory类,是否是影响了咱们的开发效率?若是这个Factory类可以根据咱们添加新的形状来同步更新Factory代码,岂不是就省了咱们不少时间了吗?
应该怎么作才能知足上述需求呢?在第一节中已经提到了使用APT能够帮助咱们自动生成代码。那么这个工厂类是否是可使用APT技术来自动生成呢?咱们惟一要作的事情就是新添加的形状类上加上一个注解,注解处理器就会在编译时根据注解信息自动生成ShapeFactory类的代码了,美哉,美哉!理想很丰满,可是,现实很骨感。虽然已经明确了要作什么,可是想要注解处理器帮咱们生成代码,却还有很长的路要走。不过,不当紧,接下来咱们将一步步实现注解处理器并让其自动生成Factory类。
1.定义Factory注解 首先在annotation模块下添加一个Factory的注解,Factory注解的Target为ElementType,表示它能够注解类、接口或者枚举。Retention指定为RetentionPolicy.CLASS,表示该在字节码中有效。Factory注解添加两个成员,一个Class类型的type,用来表示注解的类的类型,相同的类型表示属于同一个工厂。令需一个String类型的id,用来表示注解的类的名称。Factory注解代码以下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface Factory {
Class type();
String id();
}
复制代码
接下来咱们用@Factory去注解形状类,以下:
@Factory(id = "Rectangle", type = IShape.class)
public class Rectangle implements IShape {
@Override
public void draw() {
System.out.println("Draw a Rectangle");
}
}
... 其余形状类代码相似再也不贴出
复制代码
**2.认识AbstractProcessor **
接下来,就到了咱们本篇文章所要讲的核心了。没错,就是AbstractProcessor!咱们先在factory-compiler模块下建立一个FactoryProcessor类继承AbstractProcessor ,并重写相应的方法,代码以下:
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return super.getSupportedAnnotationTypes();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return super.getSupportedSourceVersion();
}
}
复制代码
能够看到,在这个类上添加了@AutoService注解,它的做用是用来生成META-INF/services/javax.annotation.processing.Processor文件的,也就是咱们在使用注解处理器的时候须要手动添加META-INF/services/javax.annotation.processing.Processor,而有了@AutoService后它会自动帮咱们生成。AutoService是Google开发的一个库,使用时须要在factory-compiler中添加依赖,以下:
implementation 'com.google.auto.service:auto-service:1.0-rc4'
复制代码
接下来咱们将目光移到FactoryProcessor类内部,能够看到在这个类中重写了四个方法,咱们由易到难依次来看:
(1) public SourceVersion getSupportedSourceVersion()
这个方法很是简单,只有一个返回值,用来指定当前正在使用的Java版本,一般return SourceVersion.latestSupported()便可。
(2) public Set<String> getSupportedAnnotationTypes()
这个方法的返回值是一个Set集合,集合中指要处理的注解类型的名称(这里必须是完整的包名+类名,例如com.example.annotation.Factory)。因为在本例中只须要处理@Factory注解,所以Set集合中只须要添加@Factory的名称便可。
(3) public synchronized void init(ProcessingEnvironment processingEnvironment)
这个方法用于初始化处理器,方法中有一个ProcessingEnvironment类型的参数,ProcessingEnvironment是一个注解处理工具的集合。它包含了众多工具类。例如: Filer能够用来编写新文件; Messager能够用来打印错误信息; Elements是一个能够处理Element的工具类。
在这里咱们有必要认识一下什么是Element
在Java语言中,Element是一个接口,表示一个程序元素,它能够指代包、类、方法或者一个变量。Element已知的子接口有以下几种:
PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。 ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。 TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。 VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。
接下来,我但愿你们先来理解一个新的概念,即抛弃咱们现有对Java类的理解,把Java类看做是一个结构化的文件。什么意思?就是把Java类看做一个相似XML或者JSON同样的东西。有了这个概念以后咱们就能够很容易的理解什么是Element了。带着这个概念来看下面的代码:
package com.zhpan.mannotation.factory; // PackageElement
public class Circle { // TypeElement
private int i; // VariableElement
private Triangle triangle; // VariableElement
public Circle() {} // ExecuteableElement
public void draw( // ExecuteableElement
String s) // VariableElement
{
System.out.println(s);
}
@Override
public void draw() { // ExecuteableElement
System.out.println("Draw a circle");
}
}
复制代码
如今明白了吗?不一样类型Element其实就是映射了Java中不一样的类元素!知晓这个概念后将对理解后边的代码有很大的帮助。
(4) public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
终于,到了FactoryProcessor类中最后一个也是最重要的一个方法了。先看这个方法的返回值,是一个boolean类型,返回值表示注解是否由当前Processor 处理。若是返回 true,则这些注解由此注解来处理,后续其它的 Processor 无需再处理它们;若是返回 false,则这些注解未在此Processor中处理并,那么后续 Processor 能够继续处理它们。 在这个方法的方法体中,咱们能够校验被注解的对象是否合法、能够编写处理注解的代码,以及自动生成须要的java文件等。所以说这个方法是AbstractProcessor 中的最重要的一个方法。咱们要处理的大部分逻辑都是在这个方法中完成。
了解上述四个方法以后咱们即可以初步的来编写FactoryProcessor类的代码了,以下:
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
private Types mTypeUtils;
private Messager mMessager;
private Filer mFiler;
private Elements mElementUtils;
private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mTypeUtils = processingEnvironment.getTypeUtils();
mMessager = processingEnvironment.getMessager();
mFiler = processingEnvironment.getFiler();
mElementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> annotations = new LinkedHashSet<>();
annotations.add(Factory.class.getCanonicalName());
return annotations;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 扫描全部被@Factory注解的元素
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
}
return false;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}
复制代码
上述FactoryProcessor 代码中在process方法中经过roundEnv.getElementsAnnotatedWith(Factory.class)方法已经拿到了被注解的元素的集合。正常状况下,这个集合中应该包含的是全部被Factory注解的Shape类的元素,也就是一个TypeElement。但在编写程序代码时可能有新来的同事不太了解@Factory的用途而误把@Factory用在接口或者抽象类上,这是不符合咱们的标准的。所以,须要在process方法中判断被@Factory注解的元素是不是一个类,若是不是一个类元素,那么就抛出异常,终止编译。代码以下:
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 经过RoundEnvironment获取到全部被@Factory注解的对象
for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(Factory.class)) {
if (annotatedElement.getKind() != ElementKind.CLASS) {
throw new ProcessingException(annotatedElement, "Only classes can be annotated with @%s",
Factory.class.getSimpleName());
}
TypeElement typeElement = (TypeElement) annotatedElement;
FactoryAnnotatedClass annotatedClass = new FactoryAnnotatedClass(typeElement);
...
}
return true;
}
复制代码
基于面向对象的思想,咱们能够将annotatedElement中包含的信息封装成一个对象,方便后续使用,所以,另外能够另外声明一个FactoryAnnotatedClass来解析并存放annotatedElement的相关信息。FactoryAnnotatedClass代码以下:
public class FactoryAnnotatedClass {
private TypeElement mAnnotatedClassElement;
private String mQualifiedSuperClassName;
private String mSimpleTypeName;
private String mId;
public FactoryAnnotatedClass(TypeElement classElement) {
this.mAnnotatedClassElement = classElement;
Factory annotation = classElement.getAnnotation(Factory.class);
mId = annotation.id();
if (mId.length() == 0) {
throw new IllegalArgumentException(
String.format("id() in @%s for class %s is null or empty! that's not allowed",
Factory.class.getSimpleName(), classElement.getQualifiedName().toString()));
}
// Get the full QualifiedTypeName
try { // 该类已经被编译
Class<?> clazz = annotation.type();
mQualifiedSuperClassName = clazz.getCanonicalName();
mSimpleTypeName = clazz.getSimpleName();
} catch (MirroredTypeException mte) {// 该类未被编译
DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
mQualifiedSuperClassName = classTypeElement.getQualifiedName().toString();
mSimpleTypeName = classTypeElement.getSimpleName().toString();
}
}
// ...省去getter
}
复制代码
为了生成合乎要求的ShapeFactory类,在生成ShapeFactory代码前须要对被Factory注解的元素进行一系列的校验,只有经过校验,符合要求了才能够生成ShapeFactory代码。根据需求,咱们列出以下规则:
1.只有类才能被@Factory注解。由于在ShapeFactory中咱们须要实例化Shape对象,虽然@Factory注解声明了Target为ElementType.TYPE,但接口和枚举并不符合咱们的要求。 2.被@Factory注解的类中须要有public的构造方法,这样才能实例化对象。 3.被注解的类必须是type指定的类的子类 4.id须要为String类型,而且须要在相同type组中惟一 5.具备相同type的注解类会被生成在同一个工厂类中
根据上面的规则,咱们来一步步完成校验,以下代码:
private void checkValidClass(FactoryAnnotatedClass item) throws ProcessingException {
TypeElement classElement = item.getTypeElement();
if (!classElement.getModifiers().contains(Modifier.PUBLIC)) {
throw new ProcessingException(classElement, "The class %s is not public.",
classElement.getQualifiedName().toString());
}
// 若是是抽象方法则抛出异常终止编译
if (classElement.getModifiers().contains(Modifier.ABSTRACT)) {
throw new ProcessingException(classElement,
"The class %s is abstract. You can't annotate abstract classes with @%",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName());
}
// 这个类必须是在@Factory.type()中指定的类的子类,不然抛出异常终止编译
TypeElement superClassElement = mElementUtils.getTypeElement(item.getQualifiedFactoryGroupName());
if (superClassElement.getKind() == ElementKind.INTERFACE) {
// 检查被注解类是否实现或继承了@Factory.type()所指定的类型,此处均为IShape
if (!classElement.getInterfaces().contains(superClassElement.asType())) {
throw new ProcessingException(classElement,
"The class %s annotated with @%s must implement the interface %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
}
} else {
TypeElement currentClass = classElement;
while (true) {
TypeMirror superClassType = currentClass.getSuperclass();
if (superClassType.getKind() == TypeKind.NONE) {
// 向上遍历父类,直到Object也没获取到所需父类,终止编译抛出异常
throw new ProcessingException(classElement,
"The class %s annotated with @%s must inherit from %s",
classElement.getQualifiedName().toString(), Factory.class.getSimpleName(),
item.getQualifiedFactoryGroupName());
}
if (superClassType.toString().equals(item.getQualifiedFactoryGroupName())) {
// 校验经过,终止遍历
break;
}
currentClass = (TypeElement) mTypeUtils.asElement(superClassType);
}
}
// 检查是否由public的无参构造方法
for (Element enclosed : classElement.getEnclosedElements()) {
if (enclosed.getKind() == ElementKind.CONSTRUCTOR) {
ExecutableElement constructorElement = (ExecutableElement) enclosed;
if (constructorElement.getParameters().size() == 0 &&
constructorElement.getModifiers().contains(Modifier.PUBLIC)) {
// 存在public的无参构造方法,检查结束
return;
}
}
}
// 为检测到public的无参构造方法,抛出异常,终止编译
throw new ProcessingException(classElement,
"The class %s must provide an public empty default constructor",
classElement.getQualifiedName().toString());
}
复制代码
若是经过上述校验,那么说明被@Factory注解的类是符合咱们的要求的,接下来就能够处理注解信息来生成所需代码了。可是本着面向对象的思想,咱们还需声明FactoryGroupedClasses来存放FactoryAnnotatedClass,而且在这个类中完成了ShapeFactory类的代码生成。FactoryGroupedClasses 代码以下:
public class FactoryGroupedClasses {
private static final String SUFFIX = "Factory";
private String qualifiedClassName;
private Map<String, FactoryAnnotatedClass> itemsMap = new LinkedHashMap<>();
public FactoryGroupedClasses(String qualifiedClassName) {
this.qualifiedClassName = qualifiedClassName;
}
public void add(FactoryAnnotatedClass toInsert) {
FactoryAnnotatedClass factoryAnnotatedClass = itemsMap.get(toInsert.getId());
if (factoryAnnotatedClass != null) {
throw new IdAlreadyUsedException(factoryAnnotatedClass);
}
itemsMap.put(toInsert.getId(), toInsert);
}
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
// Generate java file
...
}
}
复制代码
接下来将全部的FactoryGroupedClasses都添加到集合中去
private Map<String, FactoryGroupedClasses> factoryClasses = new LinkedHashMap<>();
// ...
FactoryGroupedClasses factoryClass = factoryClasses.get(annotatedClass.getQualifiedFactoryGroupName());
if (factoryClass == null) {
String qualifiedGroupName = annotatedClass.getQualifiedFactoryGroupName();
factoryClass = new FactoryGroupedClasses(qualifiedGroupName);
factoryClasses.put(qualifiedGroupName, factoryClass);
}
factoryClass.add(annotatedClass);
// ...
复制代码
OK!到目前为止,全部的准备工做都已经完成了。接下来就是根据注解信息来生成ShapeFactory类了,有没有很兴奋?遍历factoryClasses集合,并调用FactoryGroupedClasses类的generateCode()方法来生成代码了:
for (FactoryGroupedClasses factoryClass : factoryClasses.values()) {
factoryClass.generateCode(mElementUtils, mFiler);
}
复制代码
但是,当咱们去掉用generateCode(mElementUtils, mFiler)方法的时候.....纳尼?仍是一个空方法,咱们还没由实现呢!笑哭😂...
到此为止,咱们惟一剩余的需求就是生成ShapeFactory类了。上一节中咱们在FactoryProcessor类的init(ProcessingEnvironment processingEnvironment)方法中经过processingEnvironment拿到了Filer,而且咱们也提到经过Filer能够用来编写文件,便可以经过Filer来生成咱们所须要的ShapeFactory类。可是,直接使用Filer须要咱们手动拼接类的代码,极可能一不当心写错了一个字母就导致所生成的类是无效的。所以,咱们须要来认识一下JavaPoet这个库。 JavaPoet是square公司的一个开源框架JavaPoet,由Jake Wharton大神所编写。JavaPoet能够用对象的方式来帮助咱们生成类代码,也就是咱们能只要把要生成的类文件包装成一个对象,JavaPoet即可以自动帮咱们生成类文件了。关于这个库的使用就不详细在这里讲解了,有须要了解的能够到github查看,使用起来很简单。
好了,步入正题,使用JavaPoet构建并自动生成ShapeFactory类的代码以下:
public void generateCode(Elements elementUtils, Filer filer) throws IOException {
TypeElement superClassName = elementUtils.getTypeElement(qualifiedClassName);
String factoryClassName = superClassName.getSimpleName() + SUFFIX;
String qualifiedFactoryClassName = qualifiedClassName + SUFFIX;
PackageElement pkg = elementUtils.getPackageOf(superClassName);
String packageName = pkg.isUnnamed() ? null : pkg.getQualifiedName().toString();
MethodSpec.Builder method = MethodSpec.methodBuilder("create")
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "id")
.returns(TypeName.get(superClassName.asType()));
method.beginControlFlow("if (id == null)")
.addStatement("throw new IllegalArgumentException($S)", "id is null!")
.endControlFlow();
for (FactoryAnnotatedClass item : itemsMap.values()) {
method.beginControlFlow("if ($S.equals(id))", item.getId())
.addStatement("return new $L()", item.getTypeElement().getQualifiedName().toString())
.endControlFlow();
}
method.addStatement("throw new IllegalArgumentException($S + id)", "Unknown id = ");
TypeSpec typeSpec = TypeSpec
.classBuilder(factoryClassName)
.addModifiers(Modifier.PUBLIC)
.addMethod(method.build())
.build();
JavaFile.builder(packageName, typeSpec).build().writeTo(filer);
}
复制代码
好了,如今项目已经能够帮咱们自动来生成须要的Java文件啦。接下来验证一下,Build一下项目,切换到project模式下,在app-->build-->generated-->source-->apt-->debug-->(package)-->factory下面就能够看到ShapeFactory类,以下图:
到此为止,本篇文章就告一段落了。相信看完本篇文章必定大有所获,由于掌握了APT技术以后,再去研究使用APT的第三方框架源码,必定会游刃有余,事半功倍。
因为本篇文章结构比较复杂且代码也较多,项目的源码已经放在文章末尾,可做参考。
参考资料