Android Annotation-让你的代码更加优雅(二)作一个Java诗人(JavaPoet)

上篇回顾

上一篇咱们按照思惟导图,介绍了注解的基础知识,如何定义一个注解,提示性注解,运行时注解的写法和用法。没有看过第一篇,又对注解知识相对陌生的同窗,建议先食用第一篇。本篇将重点介绍编译期注解,自动生成Java文件相关内容。第一篇传送门:java

Android Annotation-让你的代码更加优雅(一)android

本篇食用路线

照例,这里先给出本篇的学习导图。方便你们掌握学习大纲。本章照例会先给出一些用来处理编译期注解的基础类和方法,而后经过一些具体的例子学习如何利用编译期注解来实现一些便捷功能。本篇的食用时间可能稍长,建议收藏后慢慢食用。git

编译期静态处理-作一个Java诗人

JavaPoet简介

JavaPoet是square公司的开源库,传送门见下面。从名字就能够看出,Java诗人,即JavaPoet是一个经过注解生成java文件的库。咱们能够利用注解,运用JavaPoet来生成一些重复的模板代码。从而大大提升咱们编程效率。像咱们熟知的ButterKnife,就是经过这种方法来简化代码编写的。在JavaPoet使用过程当中,也须要用到一些Java API,咱们会在后文一并讲解。github

Github-JavaPoet编程

使用时,引入依赖就能够了:json

compile 'com.squareup:javapoet:1.7.0'
复制代码

“诗人”眼中的结构化Java文件

了解编译原理的同窗都知道,在编译器眼中,代码文件其实就是按必定语法编写的结构化数据。编译器在处理Java文件时,也是按照既定的语法,分析Java文件的结构化数据。结构化数据就是咱们平常编写Java文件时用到的基本元素。在Java中,对于编译器来讲代码中的元素结构是基本不变的,例如组成代码的基本元素包括包、类、函数、字段、变量等,JDK为这些元素定义了一个基类,也就是Element,咱们用Element及其子类来表示这些基本元素,它共用5个子类:bash

类名 表达的元素
PackageElement 表示一个包程序元素,能够获取到包名等
TypeElement 表示一个类或接口程序元素
VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量、类成员变量或异常参数
ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注解类型元素
TypeParameterElement 表示通常类、接口、方法或构造方法元素的泛型参数

经过一个例子来明确一下:app

package com.xm.test;    // 包名,PackageElement

public class Test< // 类名,TypeElement T // 泛型参数,TypeParameterElement > {     

    private int a;       // 成员变量,VariableElement
    private Test other;  // 成员变量,VariableElement

    public Test () {}    // 成员方法,ExecuteableElement
    public void setA ( // 成员方法,ExecuteableElement int newA // 方法参数,VariableElement ) {
        String test;     // 局部变量,VariableElement
    }
}
复制代码

当编译器操做Java文件中的元素时,就是经过上面这些类来进行操做的。即咱们想经过JavaPoet来生成Java文件时,就可使用这些子类来表达结构化程序的元素。任何一个Element类对象,均可以根据实际状况,强转成对应的子类。而Element类,其实是一个接口,它定义了一套方法,咱们来一块儿看一下。ide

public interface Element extends AnnotatedConstruct {  
    /** * 返回此元素定义的类型 * 例如,对于通常类元素 Clazz<P extends People>,返回参数化类型 Clazz<P> */  
    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();  
    
    boolean equals(Object var1);

    int hashCode();
  
    /** * 返回直接存在于此元素上的注解 * 要得到继承的注解,可以使用 getAllAnnotationMirrors */  
    List<? extends AnnotationMirror> getAnnotationMirrors();  
  
    /** * 返回此元素针对指定类型的注解(若是存在这样的注解),不然返回 null。注解能够是继承的,也能够是直接存在于此元素上的 */   
    <A extends Annotation> A getAnnotation(Class<A> annotationType); 
     
    <R, P> R accept(ElementVisitor<R, P> var1, P var2);
}
复制代码

“诗人”的大脑-APT(Annotation Processor Tool)注解处理器

APT,说其是诗人的大脑,是由于咱们整个代码生成任务的核心,须要用到注解的处理器提供的方法和入口。在整个流程的核心部分,都是由APT来实现和控制的。APT是一种处理注解的工具,确切的说它是javac的一个工具,它用来在编译时扫描和处理注解,一个注解的注解处理器,以java代码(或者编译过的字节码)做为输入,生成.java文件做为输出,核心是交给本身定义的处理器去处理。实际上,APT在编译期留出了一个供咱们编程的一套模板接口。咱们经过实现处理器中的方法,就能够编写本身的注解处理流程了。函数

APT核心-AbstractProcessor

每一个自定义的注解处理器,都要继承虚处理器AbstractProcessor,来实现其几个关键方法。

虚处理器AbstractProcessor的几个关键方法:

public class MyProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment env){ }

    @Override
    public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { }

    @Override
    public Set<String> getSupportedAnnotationTypes() { }

    @Override
    public SourceVersion getSupportedSourceVersion() { }
}
复制代码

当咱们实现自定义的注解处理器时,上述的这几个方法,是必需要实现的。下面重点介绍一下这四个方法:

  • init(ProcessingEnvironment env):每个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供不少有用的工具类,如Elements, Types和Filer。

  • process(Set<? extends TypeElement> annotations, RoundEnvironment env):这至关于每一个处理器的主函数main()。你在这里写你的扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可让你查询出包含特定注解的被注解元素。这是一个布尔值,代表注解是否已经被处理器处理完成,官方原文whether or not the set of annotations are claimed by this processor,一般在处理出现异常直接返回false、处理完成返回true。

  • getSupportedAnnotationTypes():必需要实现;用来表示这个注解处理器是注册给哪一个注解的。返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。

  • getSupportedSourceVersion():用来指定你使用的Java版本。一般这里返回SourceVersion.latestSupported(),你也可使用SourceVersion_RELEASE_六、七、8注册处理器版本。

因为注解处理器是javac的工具,所以咱们必须将自定义的处理器注册到javac中,方法是咱们须要提供一个.jar文件,打包你的注解处理器到此文件中,而且在jar中,须要打包一个特定的文件 javax.annotation.processing.Processor到META-INF/services路径下 。而这一切都是极为繁琐的。幸亏谷歌给咱们开发了AutoService注解,你只须要引入这个依赖,而后在你的处理器类上加上注解:

@AutoService(Processor.class)
复制代码

而后咱们就能够自动生成文件,并打包进jar中。省去了不少麻烦事儿。

那么上面咱们介绍完处理器相关内容,下面咱们再来看一看APT还为咱们提供了哪些其它工具。

APT提供的四个辅助工具

这四个工具,咱们经过在AbstractProcessor的实现类中,经过ProcessingEnvironment便可得到:

private Filer mFiler;
    private Elements mElementUtils;
    private Messager mMessager;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        mElementUtils = processingEnv.getElementUtils();
        mMessager = processingEnv.getMessager();
        mFiler = processingEnv.getFiler();
    }
复制代码

Filer

从名字看得出来,与文件相关的操做会用到。通常配合JavaPoet来生成Java文件

Messager

它提供给注解处理器一个报告错误、警告信息的途径。当咱们自定义的注解处理器运行时报错时,那么运行注解处理器的JVM也会崩溃,打印出一些不容易被应用开发者读懂的日志信息。这时,咱们能够借助Messager输出一些调试信息,以更直观的方式提示程序运行的错误。

Types

Types是一个用来操做TypeMirror的工具。TypeMirror是Element中经过adType()方法获得的一个对象。它保存了元素的具体信息,好比Element是一个类,那么其成员详细信息就保存在TypeMirror中。

Elements

Elements是一个用来处理Element的工具。这里不详细展开了。用到的时候会提到。

“诗人”的工具箱

JavaPoet为咱们提供了编译期经过操做Java文件结构元素,依据注解生成Java文件的便捷方法。那么如何来生成呢?咱们先有必要来了解一下JavaPoet为咱们提供了哪些工具。

JavaPoet为咱们提供的四个表达Java文件元素的经常使用类

这些用来表达Java文件元素的类,其实和上面说的Element有殊途同归之妙。如今无法具体理解不要紧,后面有例子。

类名 含义
MethodSpec 表明一个构造函数或方法声明
TypeSpec 表明一个类,接口,或者枚举声明
FieldSpec 表明一个成员变量,一个字段声明
ParameterSpec 表明一个参数,可用来生成参数
AnnotationSpec 表明一个注解
JavaFile 包含一个顶级类的Java文件

“诗人”实战

咱们先来总体看一个简单的例子,而后再拓展到各类状况。

一个例子

假如咱们定义了以下一个注解,运用上一篇咱们学过的知识:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Xnpe {
    String value();
}
复制代码

接下来实现注解处理器:

@AutoService(Processor.class)
public class XnpeProcess extends AbstractProcessor {

    private Filer filer;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (TypeElement element : annotations) {
            if (element.getQualifiedName().toString().equals(Xnpe.class.getCanonicalName())) {
                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();

                try {
                    JavaFile javaFile = JavaFile.builder("com.xm", helloWorld)
                            .addFileComment(" This codes are generated automatically. Do not modify!")
                            .build();
                    javaFile.writeTo(filer);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return true;
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(Xnpe.class.getCanonicalName());
        return annotations;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}
复制代码

这里须要一点耐心了,乍看起来有点多,但实际上比较简单。这里咱们总结出实现自定义注解处理器的几个关键步骤:

  1. 为注解处理器增长@AutoService注解。即@AutoService(Processor.java)
  2. 实现上文说的自定义注解经常使用到的四个方法,即init()、process()、getSupportedAnnotationTypes和getSupportedSourceVersion。
  3. 编写处理注解的逻辑。

本例中,咱们先来重点看第二条,即四个大方法的实现。重点在处理方法上,即process()方法。咱们拿出其中的核心部分作一个讲解。

MethodSpec main = MethodSpec.methodBuilder("main") //MethodSpec固然是methodBuilder,即建立方法。
    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//增长限定符
    .returns(void.class)                           //指定返回值类型
    .addParameter(String[].class, "args")          //指定方法的参数
    .addStatement("$T.out.println($S)", System.class, "Hello, I am Poet!")//添加逻辑代码。
    .build();                                      //建立
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld") //TypeSpec构建Class
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL) //增长限定符
    .addMethod(main)                               //将刚才建立的main方法添加进类中。
    .build();                                      //建立
复制代码

是否是流程上很容易理解。MethodSpec是用来生成方法的,详细解释可参加代码上的注释。

细心的你也许注意到了,代码中有些$T等字样的东东,这个又是什么呢?下面咱们经过几个小例子,一方面来了解一下Poet中的一些占位符,另外一方面也熟悉一下经常使用的方法。

经常使用方法

addCodeaddStatement用来增长代码
MethodSpec main = MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + " total += i;\n"
        + "}\n")
    .build();
复制代码

生成的是

void main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += i;
  }
}
复制代码
  • addCode用于增长极简代码。即代码中仅包含纯Java基础类型和运算。
  • addStatement用于增长一些须要import方法的代码。如上面的.addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!") 就须要使用.addStatement来声明。
beginControlFlow和endControlFlow,流控方法

流控方法主要用来实现一些流控代码的添加,比上面的add方法看着美观一点。好比上面的代码,能够改写为:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();
复制代码

占位符

$L字面量(Literals)

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 0")
      .beginControlFlow("for (int i = $L; i < $L; i++)", from, to)
      .addStatement("result = result $L i", op)
      .endControlFlow()
      .addStatement("return result")
      .build();
}
复制代码

当咱们传参调用时,coputeRange("test", 0, 10, "+")它能生成的代码以下:

int test(){
    int result = 0;
    for(int i = 0; i < 10; i++) {
        result = result + i;
    }
    return result;
}
复制代码

$S 字符串常量(String)

这个比较容易理解,这里就不赘述了。

$T 类型(Types)

MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build(); //建立today方法
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build(); //建立HelloWorld类
JavaFile javaFile = JavaFile.builder("com.xm.helloworld", helloWorld).build();
javaFile.writeTo(System.out);//写java文件
复制代码

生成的代码以下,咱们看到,它会自动导入所需的包。这也是咱们使用占位符的好处,也是使用JavaPoet的一大好处。

package com.xm.helloworld;

import java.util.Date;

public final class HelloWorld {
  Date today() {
    return new Date();
  }
}
复制代码

若是咱们想要导入本身写的类怎么办?上面的例子是传入系统的class,这里也提供一种方式,经过ClassName.get(”类的路径”,”类名“),结合$T能够生成

ClassName testClass = ClassName.get("com.xm", "TestClass");
ClassName list = ClassName.get("java.util", "List");
ClassName arrayList = ClassName.get("java.util", "ArrayList");
TypeName listOftestClasses = ParameterizedTypeName.get(list, testClass);

MethodSpec xNpe = MethodSpec.methodBuilder("xNpe")
    .returns(listOftestClasses)
    .addStatement("$T result = new $T<>()", listOftestClasses, arrayList)
    .addStatement("result.add(new $T())", testClass)
    .addStatement("result.add(new $T())", testClass)
    .addStatement("result.add(new $T())", testClass)
    .addStatement("return result")
    .build();
复制代码

生成的代码以下:

package com.xm.helloworld;

import com.xm.TestClass;
import java.util.ArrayList;
import java.util.List;

public final class HelloWorld {
  List<TestClass> xNpe() {
    List<TestClass> result = new ArrayList<>();
    result.add(new TestClass());
    result.add(new TestClass());
    result.add(new TestClass());
    return result;
  }
}
复制代码

Javapoet 一样支持import static,经过addStaticImport来添加:

JavaFile.builder("com.xm.helloworld", hello)
    .addStaticImport(TestClass, "START")
    .addStaticImport(TestClass2, "*")
    .addStaticImport(Collections.class, "*")
    .build();
复制代码

$N 命名(Names)

一般指咱们本身生成的方法名或者变量名等等。好比这样的代码:

public String byteToHex(int b) {
  char[] result = new char[2];
  result[0] = hexDigit((b >>> 4) & 0xf);
  result[1] = hexDigit(b & 0xf);
  return new String(result);
}

public char hexDigit(int i) {
  return (char) (i < 10 ? i + '0' : i - 10 + 'a');
}
复制代码

这个例子中,咱们在byteToHex中须要调用到hexDigit方法,咱们就能够用$N来表示这种引用。而后经过传递方法名,达到这种调用语句的生成。

MethodSpec hexDigit = MethodSpec.methodBuilder("hexDigit")
    .addParameter(int.class, "i")
    .returns(char.class)
    .addStatement("return (char) (i < 10 ? i + '0' : i - 10 + 'a')")
    .build();

MethodSpec byteToHex = MethodSpec.methodBuilder("byteToHex")
    .addParameter(int.class, "b")
    .returns(String.class)
    .addStatement("char[] result = new char[2]")
    .addStatement("result[0] = $N((b >>> 4) & 0xf)", hexDigit)
    .addStatement("result[1] = $N(b & 0xf)", hexDigit)
    .addStatement("return new String(result)")
    .build();
复制代码

自顶向下,构建Java类的元素

普通方法

咱们在定义方法时,也要对方法增长一些修饰符,如Modifier.ABSTRACT。能够经过addModifiers()方法:

MethodSpec test = MethodSpec.methodBuilder("test")
    .addModifiers(Modifier.ABSTRACT, Modifier.PROTECTED)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addMethod(test)
    .build();
复制代码

将会生成以下代码:

public abstract class HelloWorld {
  protected abstract void test();
}
复制代码

构造器

构造器只不过是一个特殊的方法,所以可使用MethodSpec来生成构造器方法。使用constrctorBuilder来生成:

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;
  }
}
复制代码

各类参数

参数也有本身的一个专用类ParameterSpec,咱们可使用ParameterSpec.builder()来生成参数,而后MethodSpec的addParameter去使用,这样更加优雅。

ParameterSpec android = ParameterSpec.builder(String.class, "android")
    .addModifiers(Modifier.FINAL)
    .build();

MethodSpec welcomeOverlords = MethodSpec.methodBuilder("test")
    .addParameter(android)
    .addParameter(String.class, "robot", Modifier.FINAL)
    .build();
复制代码

生成的代码

void test(final String android, final String robot) {
}
复制代码

字段,成员变量

可使用FieldSpec去声明字段,而后加到类中:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(android)
    .addField(String.class, "robot", Modifier.PRIVATE, Modifier.FINAL)
    .build();
复制代码

生成代码:

public class HelloWorld {
  private final String android;
  private final String robot;
}
复制代码

一般Builder能够更加详细的建立字段的内容,好比javadoc、annotations或者初始化字段参数等,如:

FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .initializer("$S + $L", "Pie v.", 9.0)//初始化赋值
    .build();
复制代码

对应生成的代码:

private final String android = "Pie v." + 9.0;
复制代码

接口

接口方法必须是PUBLIC ABSTRACT而且接口字段必须是PUBLIC STATIC FINAL ,使用TypeSpec.interfaceBuilder以下:

TypeSpec helloWorld = TypeSpec.interfaceBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC)
    .addField(FieldSpec.builder(String.class, "KEY_START")
        .addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
        .initializer("$S", "start")
        .build())
    .addMethod(MethodSpec.methodBuilder("beep")
        .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
        .build())
    .build();
复制代码

生成的代码以下:

public interface HelloWorld {
  String KEY_START = "start";
  void beep();
}
复制代码

枚举类型

使用TypeSpec.enumBuilder来建立,使用addEnumConstant来添加枚举值:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK")
    .addEnumConstant("SCISSORS")
    .addEnumConstant("PAPER")
    .build();
复制代码

生成的代码

public enum Roshambo {
  ROCK,
  SCISSORS,
  PAPER
}
复制代码

匿名内部类

须要使用Type.anonymousInnerClass(""),一般可使用$L占位符来指代:

TypeSpec comparator = TypeSpec.anonymousClassBuilder("")
    .addSuperinterface(ParameterizedTypeName.get(Comparator.class, String.class))
    .addMethod(MethodSpec.methodBuilder("compare")
        .addAnnotation(Override.class)
        .addModifiers(Modifier.PUBLIC)
        .addParameter(String.class, "a")
        .addParameter(String.class, "b")
        .returns(int.class)
        .addStatement("return $N.length() - $N.length()", "a", "b")
        .build())
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addMethod(MethodSpec.methodBuilder("sortByLength")
        .addParameter(ParameterizedTypeName.get(List.class, String.class), "strings")
        .addStatement("$T.sort($N, $L)", Collections.class, "strings", comparator)
        .build())
    .build();
复制代码

生成代码:

void sortByLength(List<String> strings) {
  Collections.sort(strings, new Comparator<String>() {
    @Override
    public int compare(String a, String b) {
      return a.length() - b.length();
    }
  });
}
复制代码

定义匿名内部类的一个特别棘手的问题是参数的构造。在上面的代码中咱们传递了不带参数的空字符串。TypeSpec.anonymousClassBuilder("")

注解

注解使用起来比较简单,经过addAnnotation就能够添加:

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hello XiaoMing")
    .build();
复制代码

生成代码

@Override
public String toString() {
  return "Hello XiaoMing";
}
复制代码

经过AnnotationSpec.builder() 能够对注解设置属性:

MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(Headers.class)
        .addMember("accept", "$S", "application/json; charset=utf-8")
        .addMember("userAgent", "$S", "Square Cash")
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class)
    .build();
复制代码

代码生成以下

@Headers(
    accept = "application/json; charset=utf-8",
    userAgent = "Square Cash"
)
LogReceipt recordEvent(LogRecord logRecord);
复制代码
MethodSpec logRecord = MethodSpec.methodBuilder("recordEvent")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addAnnotation(AnnotationSpec.builder(HeaderList.class)
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "Accept")
            .addMember("value", "$S", "application/json; charset=utf-8")
            .build())
        .addMember("value", "$L", AnnotationSpec.builder(Header.class)
            .addMember("name", "$S", "User-Agent")
            .addMember("value", "$S", "Square Cash")
            .build())
        .build())
    .addParameter(LogRecord.class, "logRecord")
    .returns(LogReceipt.class
    .build
复制代码

生成Java文件

生成Java文件,咱们须要用到上文提到的FilerElements。注意下面这段代码,重要的是包名,类名的指定。这里生成的文件名,通常会遵循某个约定,以便事先写好反射代码。

//获取待生成文件的包名
public String getPackageName(TypeElement type) {
    return mElementUtils.getPackageOf(type).getQualifiedName().toString();
}

//获取待生成文件的类名
private static String getClassName(TypeElement type, String packageName) {
    int packageLen = packageName.length() + 1;
    return type.getQualifiedName().toString().substring(packageLen).replace('.', '$');
}

//生成文件
private void writeJavaFile() {
    String packageName = getPackageName(mClassElement);
    String className = getClassName(mClassElement, packageName);
    ClassName bindClassName = ClassName.get(packageName, className);
    TypeSpec finderClass = TypeSpec.classBuilder(bindClassName.simpleName() + "$$Injector")
        .addModifiers(Modifier.PUBLIC)
        .addSuperinterface(ParameterizedTypeName.get(TypeUtil.INJECTOR,
                                                     TypeName.get(mClassElement.asType())))
        .addMethod(methodBuilder.build())
        .build();
    //使用JavaFile的builder来生成java文件
    JavaFile.builder(packageName, finderClass).build().writeTo(mFiler);
}
复制代码

总结

经过两篇的学习,咱们熟悉了Java注解的用途,写法,以及如何用它为咱们的编码或程序服务。本篇罗列了不少具体的例子,但愿能覆盖到平常你们使用的方方面面,你们也能够收藏本文,在使用JavaPoet的时候进行参照。

小铭出品,必属精品

欢迎关注xNPE技术论坛,更多原创干货每日推送。

相关文章
相关标签/搜索