本文主要用来理解编译注解框架。
理解两种关系
编译关系:处于编译关系的库不会被引入应用,但是会进行编译,会获得编译期的上下文环境,一般用这个库来做注解处理和代码生成。对应关键字:
annotationProcessor project(':ioc-compiler')
依赖关系:常见的既会被编译也会被依赖进应用的库。
compile project(':ioc-compiler')
理解三种库:
注解库:提供注解类,既会被编译库依赖,也会被应用依赖。
编译库:依赖注解库,提供编译时生成文件的能力,与主app之间是编译关系。
API库:依赖注解库,提供具体的api接口,负责具体的功能实现,与主app之间是依赖关系。
图示如下:
auto-service使用方法
gradle文件中 引入compile ‘com.google.auto.service:auto-service:1.0-rc2’,这是google提供的编译处理的库,继承AbstractProcessor自定义编译处理器,然后通过注解@AutoService(Processor.class),就会在编译时自动执行。
这个库以编译库的形式存在,理论上不应该被依赖进工程中。
@AutoService(Processor.class)
// @SupportedAnnotationTypes(Test.class.getCanonicalName()) 把支持的类型添加进去
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class IocProcessor extends AbstractProcessor {
/** * 日志打印类 **/
private Messager messager;
/** * 元素工具类 **/
private Elements elementsUtils;
/** 写java文件的类 **/
private Filer mFiler;
/** * 保存所有的要生成的注解文件信息 * */
private Map<String, ProxyInfo> mProxyMap = new HashMap<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
messager = processingEnv.getMessager();
elementsUtils = processingEnv.getElementUtils();
mFiler = processingEnv.getFiler();
}
/**
* 此方法用来设置支持的注解类型,没有设置的无效(获取不到) 同上方注解
**/
@Override
public Set<String> getSupportedAnnotationTypes() {
HashSet<String> supportTypes = new LinkedHashSet<>();
// 把支持的类型添加进去
supportTypes.add(Test.class.getCanonicalName());
return supportTypes;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 获取全部的带有Test注解的Element
Set<? extends Element> elesWidthBind = roundEnv.getElementsAnnotatedWith(Test.class);
for (Element element : elesWidthBind) {
// element代表当前注解所代表的元素类型,如果是Field,则为属性元素VariableElement
// 如果是Class,则代表类元素TypeElement
// 其它还有:ExecutableElement方法元素、Parameterizable参数元素
Test bindAnnotation = element.getAnnotation(Test.class); // 获取注解对象
if (element.getKind() == ElementKind.Class) {
TypeElement typeElement = (TypeElement) element;
String fqClassName = typeElement.getQualifiedName().toString(); // 获取类名
}
}
// 以上代码代表可以编译时获取类和注解的所有信息
// 以下代码为生成java文件代码
for (String key : mProxyMap.keySet()) {
ProxyInfo proxyInfo = mProxyMap.get(key);
try {
JavaFileObject jfo = mFiler.createSourceFile(proxyInfo.getProxyClassFullName(), proxyInfo.getTypeElement()); // 创建文件
Writer writer = jfo.openWriter();
writer.write(proxyInfo.generateJavaCode()); // 写类文件
writer.flush();
writer.close();
} catch (IOException e) {
error(proxyInfo.getTypeElement(), "Unable to write injector for type %s: %s ", proxyInfo.getTypeElement(), e.getMessage());
}
}
return true;
}
}
1、
2、 在AbstractProcessor的核心方法process(Set annotations, RoundEnvironment roundEnv)有两个参数:
Set annotations 包含所有使用的注解的信息,例如BindView,ContentView
RoundEnvironment roundEnv包含所有被注解的元素,例如类,属性等
3、JavaFileObject是java文件对象,可以直接创建,通过流的形式,对文件进行编写。
annotationProcessor和apt的关系
上面提到的编译库:ioc-compiler,应该是以编译的方式引入主工程,annotationProcessor和apt是两个版本的插件,在gradle2.2版本之前,使用apt插件的方式引入,之后便内置为开发框架,具体使用方法如下:
apt
1、project的gradle文件中,配置classpath
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:2.3.0"
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
2、主app目录下的gradle文件中
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
apt project(':ioc-compiler')
compile project(':ioc-anotation')
}
执行Build->Rebuild project生效
annotationProcessor
dependencies {
annotationProcessor project(':ioc-compiler')
compile project(':ioc-anotation')
}
备注:
ioc-compiler为上文提到的编译库,包含AutoService注解类及类代码生成逻辑。
ioc-anotation为注解库,这个库既会被编译库依赖,也会被app主工程依赖。
注意不要把ioc-anotation的注解类放在ioc-compiler中,这样无法编译通过。
理解Element
/**
* 表示一个程序元素,比如包、类或者方法,有如下几种子接口:
* ExecutableElement:表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素 ;
* PackageElement:表示一个包程序元素;
* TypeElement:表示一个类或接口程序元素;
* TypeParameterElement:表示一般类、接口、方法或构造方法元素的形式类型参数;
* VariableElement:表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数
*/
public interface Element extends AnnotatedConstruct {
/**
* 返回此元素定义的类型
* 例如,对于一般类元素 C<N extends Number>,返回参数化类型 C<N>
*/
TypeMirror asType();
/**
* 返回此元素的种类:包、类、接口、方法、字段...,如下枚举值
* PACKAGE, ENUM, CLASS, ANNOTATION_TYPE, INTERFACE, ENUM_CONSTANT, FIELD, PARAMETER, LOCAL_VARIABLE, EXCEPTION_PARAMETER,
* METHOD, CONSTRUCTOR, STATIC_INIT, INSTANCE_INIT, TYPE_PARAMETER, OTHER, RESOURCE_VARIABLE;
*/
ElementKind getKind();
/**
* 返回此元素的修饰符,如下枚举值
* PUBLIC, PROTECTED, PRIVATE, ABSTRACT, DEFAULT, STATIC, FINAL,
* TRANSIENT, VOLATILE, SYNCHRONIZED, NATIVE, STRICTFP;
*/
Set<Modifier> getModifiers();
/**
* 返回此元素的简单名称,例如
* 类型元素 java.util.Set<E> 的简单名称是 "Set";
* 如果此元素表示一个未指定的包,则返回一个空名称;
* 如果它表示一个构造方法,则返回名称 "<init>";
* 如果它表示一个静态初始化程序,则返回名称 "<clinit>";
* 如果它表示一个匿名类或者实例初始化程序,则返回一个空名称
*/
Name getSimpleName();
/**
* 返回封装此元素的最里层元素。
* 如果此元素的声明在词法上直接封装在另一个元素的声明中,则返回那个封装元素;
* 如果此元素是顶层类型,则返回它的包;
* 如果此元素是一个包,则返回 null;
* 如果此元素是一个泛型参数,则返回 null.
*/
Element getEnclosingElement();
/**
* 返回此元素直接封装的子元素
*/
List<? extends Element> getEnclosedElements();
/**
* 返回直接存在于此元素上的注解
* 要获得继承的注解,可使用 getAllAnnotationMirrors
*/
@Override
List<? extends AnnotationMirror> getAnnotationMirrors();
/**
* 返回此元素针对指定类型的注解(如果存在这样的注解),否则返回 null。注解可以是继承的,也可以是直接存在于此元素上的
*/
@Override
<A extends Annotation> A getAnnotation(Class<A> annotationType);
}
Element可以代表包、类、接口、方法、字段等多种元素种类,具体看getKind()方法中所指代的种类,每个Element 代表一个静态的、语言级别的构件。可以通过getKind判断并强转成对应的子类,可以获得各自特有的方法。
如何理解asType返回的TypeMirror?TypeMirror中也有个getKind方法返回TypeKind可以返回巨化的类型,比如ElementKind为Field,TypeKind返回的就可能是String;ElementKind为Class,TypeKind返回为Error。
TypeElement表示一个类或接口程序元素;PackageElement表示一个包程序元素;ExecutableElement表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素;VariableElement表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数 ;TypeParameterElement表示一般类、接口、方法或构造方法元素的泛型参数。
应用
butterknife使用的就是上面的技术方案,但GreenDao3.x使用了自己开发的插件,将插件引入后,只需要简单的注解,就可以生成所有的相关表代码。猜测在插件代码中,通过编译期获取所有的class文件及注解信息,进行相关代码生成的操作,相当于这是另一条实现思路,但因为插件代码暂时没有开源,所以这里暂时不做具体分析。
https://github.com/greenrobot/greenDAO
https://github.com/JakeWharton/butterknife
JavaPoet
JavaPoet
是一个用来生成 .java源文件的Java API,中央仓库依赖com.squareup:javapoet:1.9.0
。
先看个样例:
MethodSpec main = MethodSpec.methodBuilder("main")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(String[].class, "args")
.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.writeTo(System.out);
生成java文件的内容为:
package com.example.helloworld;
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
}
添加一个方法
MethodSpec main = MethodSpec.methodBuilder("main")
.returns(int.class)
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n"
+ "return total;")
.build();
MethodSpec main = MethodSpec.methodBuilder("main")
.returns(int.class)
.addStatement("int total = 0")
.beginControlFlow("for (int i = 0; i < 10; i++)")
.addStatement("total += i")
.endControlFlow()
.addStatement("return total")
.build();
以上两段代码效果相同,生成代码如下:
int main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total = total * i;
}
return total;
}
MethodSpec涉及到的api如下:
returns:设置返回值
addCode:添加一段代码
addStatement:添加一行代码,包含分号和换行
beginControlFlow + endControlFlow:提供换行符和缩进
addParameter:添加参数
举个栗子:
ParameterSpec android = ParameterSpec.builder(String.class, "android")
.addModifiers(Modifier.FINAL)
.build();
MethodSpec welcomeOverlords = MethodSpec.methodBuilder("welcomeOverlords")
.addParameter(android)
.addParameter(String.class, "robot", Modifier.FINAL)
.build();
MethodSpec还可以添加构造器。
MethodSpec flux = MethodSpec.constructorBuilder()
.addModifiers(Modifier.PUBLIC)
.addParameter(String.class, "greeting")
.addStatement("this.$N = $N", "greeting", "greeting")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(String.class, "greeting", Modifier.PRIVATE, Modifier.FINAL)
.addMethod(flux)
.build();
生成代码如下:
public class HelloWorld {
private final String greeting;
public HelloWorld(String greeting) {
this.greeting = greeting;
}
}
添加一个变量
FieldSpec android = FieldSpec.builder(String.class, "android")
.addModifiers(Modifier.PRIVATE)
.initializer("$S", "Lollipop")
.build();
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(android)
.addField(String.class, "robot", Modifier.PRIVATE)
.build();
生成代码如下:
private String android = "Lollipop";
private String robot;
生成一个类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(today)
.build();
生成一个抽象类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addMethod(today)
.build();
生成一个接口类:
TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(FieldSpec.builder(String.class, "ONLY_THING_THAT_IS_CONSTANT")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "change")
.build())
.addMethod(MethodSpec.methodBuilder("beep")
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.build())
.build();
生成代码如下:
public interface HelloWorld {
String ONLY_THING_THAT_IS_CONSTANT = "change";
void beep();
}
format
$L => int、float、double
$S => string
$T => type 举个栗子:addStatement("return new $T()", Date.class)
$N 举个栗子:
MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class, "i")
.returns(char.class)
.addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
.build();
// 表示方法的引用
addStatement("result[1] = $N(b & 0xf)", hexDigit)
Freemarker
如果觉得Poet太麻烦,还有简单的生成代码方法,就是FreeMarker。
Freemarker 是一款模板引擎,是一种基于模版生成静态文件的通用工具。中央仓库依赖compile 'org.freemarker:freemarker:2.3.23'
获取Template对象并通过Filer对象写入
Configuration config = new Configuration(Configuration.VERSION_2_3_23);
config.setClassForTemplateLoading(getClass(), "/");
Template template = config.getTemplate("xxx.ftl");
Map root = new HashMap();
object.test = "test";
root.put("test", object);
root.put("name", "muyang");
JavaFileObject fileObject = filer.createSourceFile(javaPackage + "." + javaClassName);
template.process(root, fileObject.openWriter());
对一些对象的理解
Filer filer // Filer对象见上文,有提到
String javaPackage // 生成文件所在包
String javaClassName // 生成文件类名
xxx.ftl // 模板文件
root // dataModel对象,通过一个map承载数据,可以传递任何基本类型和引用类型,用来向tpl传递数据,具体获取数据方式见下文。
xxx.ftl获取dataModel的方式
${name} = muyang
${object.test} = test
直接通过对应名称的方式获取数据。
Tpl简单语法
循环语句
<#list nameList as names>
${names}
</#list>
条件语句
<#if (names=="陈靖仇")>
他的武器是: 十五~~
<#elseif (names=="宇文拓")>
他的武器是: 十六~~
</#if>
<#if book.name?? > // 空值判断