什么是AutoValue Extension?
如之前所说AutoValue是一个编译期生成样板代码的代码生成器,但是仅限于equals,hashCode,toString方法。当你的类需要序列化时,你还是不得不自己手动写那些样板代码,为了支持更多功能,AutoValue在1.2版本中增加了Extension。你只需在你的依赖中加入相应的extension库,AutoValue就可以帮你自动生成相关代码。
AutoValue 是如何发现Extension的?
AutoValue使用了 ServiceLoader API来发现可用的Extension。
什么是ServiceLoader?
ServiceLoader是java提供的类,它是一个简单的服务提供者加载工具。一个服务是一系列众所周知的接口或(通常是抽象的)类。一个服务提供者是一个服务的特定实现。服务提供者类通常实现了服务接口并且以子类形式定义在服务类中,服务提供程序可以以扩展的形式安装在Java平台的实现中,即将jar文件放置到任何通常的扩展目录中。还可以通过将提供程序添加到应用程序的类路径或其他特定于平台的方法来提供它们。
通过在资源目录META-INF/services中放置一个提供程序配置文件来标识服务提供者。该文件的名称是服务类型的完全限定二进制名称。该文件包含一个完整的、合格的具体提供程序类的二进制名称的列表,每行一个。每个名称周围的空格和制表符以及空行都被忽略。注释字符为'#' ('\u0023',号);在每一行中,跟随第一个注释字符的所有字符都被忽略。文件必须用UTF-8编码。
例子:假设我们有一个服务类型为com.example.CodecSet,用于表示某些协议的编/解码器对的集合,有如下两个抽象方法。
abstract class CodecSet{
public abstract Encoder getEncoder(String encodingName);
public abstract Decoder getDecoder(String encodingName);
}
支持相应编/解码时返回正确的对象,不支持时返回null。假设现在有一个类 com.example.impl.StandardCodecs实现了CodecSet。那么包含它的jar文件中有一个名为META-INF/services/com.example.CodecSet的文件
文件内容是
com.example.impl.StandardCodecs
那么应用程序classpath包含该jar时 则可以通过如下代码获取到该StandardCodecs类
ServiceLoader codecSetLoader= ServiceLoader.load(CodecSet.class);
//遍历获取注册的CodecSet服务,其中就包含我们注册的StandardCodecs
for (CodecSet codec: codecSetLoader) {
}
机制
Extension采用AutoValue类似的命名,由于可能有多个可用的Extension,这些Extension采用链式处理,每个Extension只生成自己的子类,子类的简单名称会有$前缀。
假设现在有两个可用的Extension,那么AutoValue生成的类有如下三个
AutoValue_类名 继承 $AutoValue_类名 (Extension 1生成)
$AutoValue_类名 继承 $$AutoValue_类名 (Extension 2生成)
$$AutoValue_类名 AutoValue套autovalue.vm模板生成。
注意:AutoValue_类名 是final类。其他类都是抽象类并且不能为final。
源码
AutoValue中的Extension服务类
public abstract class AutoValueExtension {
/**是否要在项目中应用该Extension。true表示应用*/
public boolean applicable(Context context) {
return false;
}
/**表示由该Extension生成的类是否需要为final类*/
public boolean mustBeFinal(Context context) {
return false;
}
/**该Extension 处理的属性集合*/
public Set consumeProperties(Context context) {
return Collections.emptySet();
}
/**该Extension 处理的方法集合*/
public Set consumeMethods(Context context) {
return Collections.emptySet();
}
/**生成源代码*/
public abstract String generateClass(
Context context, String className, String classToExtend, boolean isFinal);
加载extension
加载extension在Processor的init方法中
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
if (extensions == null) {
try {
extensions =
ImmutableList.copyOf(ServiceLoader.load(AutoValueExtension.class, loaderForExtensions));
} catch (Throwable t) {
...省略异常处理
}
}
}
找到适用的extension
代码在AutoValueProcessor 的processType方法
//初始化ExtensionContext
ExtensionContext context =
new ExtensionContext(processingEnv, type, properties, abstractMethods);
//找到适用的extension
ImmutableList applicableExtensions = applicableExtensions(type, context);
适用的依据就是Extension的applicable(Context context)方法返回true。比如你的项目添加了auto-value-pacel依赖,auto-value-pacel中定义的Extension类的applicable方法会检测你的@AutoValue注解的类是否实现了Parcelable接口,实现了该接口就返回true,表明我要处理。
应用Extension
找到了适用的Extension后就需要应用它来生成代码了。
void processType(){
...找到适用的Extension
//应用Extension生成代码
int subclassDepth = writeExtensions(type, context, applicableExtensions);
}
/*参数含义
* TypeElement type 被@AutoValue注解的类
* ExtensionContext context 扩展的上下文,含有扩展需要的信息,比如包名,被注解类,被注解类属性及方法等。
* ImmutableList applicableExtensions 需要应用的Extension
*/
private int writeExtensions(
TypeElement type,
ExtensionContext context,
ImmutableList applicableExtensions) {
int writtenSoFar = 0;
//遍历应用每一个Extension
for (AutoValueExtension extension : applicableExtensions) {
//生成的类名规则是在AutoValue_XXX前添加(writtenSoFar + 1)个$符号
String parentFqName = generatedSubclassName(type, writtenSoFar + 1);
//当前extension生成的类的父类的简单名称
String parentSimpleName = TypeSimplifier.simpleNameOf(parentFqName);
String classFqName = generatedSubclassName(type, writtenSoFar);
//当前extension生成的类的简单名称
String classSimpleName = TypeSimplifier.simpleNameOf(classFqName);
boolean isFinal = (writtenSoFar == 0);
//生成源代码,由该extension实现
String source = extension.generateClass(context, classSimpleName, parentSimpleName, isFinal);
if (source != null) {
//格式化源代码
source = Reformatter.fixup(source);
//将源代码写到文件中
writeSourceFile(classFqName, source, type);
writtenSoFar++;
}
}
return writtenSoFar;
}
第三方Extension
auto-value-parcel
为实现android Parcelable接口的@AutoValue注解的值类,自动生成Parcelable接口要求的字段和方法。
auto-value-gson
...
自定义Extension
假设我们要为实现了Windable接口的AutoValue类自动生成wind方法。
看一下Windable接口
public interface Windable {
void wind();
}
注意:该类放在独立的java module中
自定义步骤:
1,继承AutoValueExtension类,在resource/META-INF/servcies创建com.google.auto.value.extension.AutoValueExtension文件,内容是自定义的AutoValueExtension的全限定名称
2,覆写applicable(Context context)方法,返回值的含义:返回true 应用该extension,否则不应用。
3,如果需要处理AutoValue类的属性或方法需要覆写consumeProperties或consumeMethods方法,返回值分别是本Extension处理的属性或方法。
4,实现generateClass方法以生成源码
创建一个java工程中,创建AutoValueWindableExtension
/**
* 检查AutoValue 类是否实现了Windable接口,实现了Windable接口,说明需要处理返回true
* @param context
* @return
*/
@Override
public boolean applicable(Context context) {
TypeElement typeElement = context.autoValueClass();
//获取AutoValue类的TypeMirror
TypeMirror autoValueTypeMirror = typeElement.asType();
Types typeUtils = context.processingEnvironment().getTypeUtils();
Elements elementUtils = context.processingEnvironment().getElementUtils();
TypeElement windableTypeElement = elementUtils
.getTypeElement("com.wind.windable.Windable");
//判断AutoValue类是否实现了Windable接口
boolean isAssigable=typeUtils.isAssignable( autoValueTypeMirror,windableTypeElement.asType());
return isAssigable;
}
/**
* 需要实现Windable接口中的wind方法,所以会消耗wind方法
* @param context
* @return
*/
@Override
public Set consumeMethods(Context context) {
ImmutableSet.Builder methods = new ImmutableSet.Builder<>();
for (ExecutableElement element : context.abstractMethods()) {
switch (element.getSimpleName().toString()) {
case "wind":
methods.add(element);
break;
}
}
return methods.build();
}
/**
* 使用JavaPoet生成java源码
* @param context
* @param className
* @param classToExtend
* @param isFinal
* @return
*/
@Override
public String generateClass(Context context, String className, String classToExtend, boolean isFinal) {
//生成wind方法
MethodSpec.Builder method = MethodSpec
.methodBuilder("wind")
.returns(TypeName.VOID)
.addModifiers(Modifier.PUBLIC)
.addAnnotation(Override.class)
.addStatement("//generate your code");
Map properties= context.properties();
List parameterSpecList=new ArrayList<>();
StringBuilder statement=new StringBuilder("super(");
int i=0;
for (Map.Entry entry:properties.entrySet()){
String key=entry.getKey();
ExecutableElement executableElement=entry.getValue();
TypeName paramTypeName=TypeName.get(executableElement.getReturnType());
parameterSpecList.add(ParameterSpec.builder(paramTypeName,key).build());
statement.append(key);
if (i
该java module的build.gradle文件
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.9.0'
compile 'com.google.auto.value:auto-value:1.5.2'
compile 'com.google.auto:auto-common:0.9'
//compile 'org.apache.commons:commons-lang3:3.4'
compileOnly 'com.google.auto.service:auto-service:1.0-rc3'
// 如果找不到javax包,可以直接引入本地jdk的jar包
compile files('/Library/Java/JavaVirtualMachines/jdk1.7.0_71.jdk/Contents/Home/jre/lib/rt.jar')
compile project(":windable")
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"
使用
app工程的build.gradle文件
compile "com.google.auto.value:auto-value-annotations:1.6.2"
annotationProcessor "com.google.auto.value:auto-value:1.6.2"
annotationProcessor project(':auto-value-windable')//自定义extension的java工程
compile project(':windable')//windable接口java工程