AutoValue扩展(Extension)

什么是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<CodecSet> 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<String> consumeProperties(Context context) {
      return Collections.emptySet();
    }
  /**该Extension 处理的方法集合*/
      public Set<ExecutableElement> 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<AutoValueExtension> 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<AutoValueExtension> applicableExtensions  需要应用的Extension
*/
private int writeExtensions(
      TypeElement type,
      ExtensionContext context,
      ImmutableList<AutoValueExtension> 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<ExecutableElement> consumeMethods(Context context) {

        ImmutableSet.Builder<ExecutableElement> 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<String, ExecutableElement> properties= context.properties();
        List<ParameterSpec> parameterSpecList=new ArrayList<>();
        StringBuilder statement=new StringBuilder("super(");
        int i=0;

        for (Map.Entry<String,ExecutableElement> 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<properties.size()-1){
                statement.append(",");
            }
            i++;
        }
        statement.append(")");
        
        //AutoValue类的构造函数
        MethodSpec.Builder constructor=MethodSpec
                .constructorBuilder()
                .addParameters(parameterSpecList)
                .addStatement(statement.toString());
        
        TypeSpec.Builder type = TypeSpec
                .classBuilder(className)
                .addModifiers(Modifier.ABSTRACT)
                .superclass(ClassName.get(context.packageName(), classToExtend))
                .addMethod(constructor.build())
                .addMethod(method.build());
        //生成源码
        String source = JavaFile.builder(context.packageName(), type.build()).build().toString();
        return source;
    }

该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工程
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,657评论 18 139
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明先生_X自主阅读 15,982评论 3 119
  • 站在十三层的楼顶 就能俯瞰这个城市。 她很安静,高低起伏的楼层, 遍地闪烁的霓虹, 从头顶带着旅人归乡的飞机 还有...
    凉温阅读 177评论 0 3
  • 0-3岁是安全感的建立期,安全感的建立主要来自妈妈 初次听到很惊讶,孩子的安全感建立来自妈妈,那妈妈如何做才能帮助...
    liuxinamy阅读 117评论 0 0
  • 本想写些什么,感慨感慨,但是明天还要工作,那就先睡觉吧,明天抽时间写。
    阳城黑公子阅读 172评论 0 0