android 注解使用

1,注解的概念

注解现在广泛的应用于android的各个开源框架中,不理解注解,我们就无法更好的提升我们的架构能力。那么什么是注解呢?注解(Annotation),是JDK5.0 引入的一种注释机制。 注解是元数据的一种形式,提供有关于程序但不属于程序本身的数据。注解对它们注解的代码的操作没有直接影响。

2,注解的定义

在java中,所有的注解都默认实现Annotation接口,接口如下:

public interface Annotation {
    boolean equals(Object var1);
 
    int hashCode();
 
    String toString();
 
    Class<? extends Annotation> annotationType();
}


要定义一个注解,和定义一个接口差不多,只不过在interface前面多了一个@

public @interface YuanZhen{
 
}

3,元注解

在定义注解时,注解类也能够使用其他的注解声明。对注解类型进行注解的注解类,我们称之为 meta-annotation(元注解)

元注解有四种类型:

最常用的是@Target 和@Retention。@Inherited和@Documented 一般很少用到,@IntDef和@StringDef多用来代替枚举,因为枚举的使用会增加内存的消耗。

3.1 @Target

@Target用来指定注解的作用范围,参数如下:


可以看一下下面的例子:

定义一个作用于方法参数的注解

@Target(ElementType.PARAMETER)//作用于方法的参数
public @interface YuanZhen {
}

当作用于类上时,就会报错,而作用于方法参数上,就可以正常运行

3.2@Retention

此元注解用来指定注解的作用时机,也就是说注解是在什么阶段有效,参数如下:

3.2.1RetentionPolicy.SOURCE

源码级别,在编译期能够获取注解与注解声明的类包括类中所有成员信息,一般用于生成额外的辅助类。

这个最常用的技术就是APT技术,好多开源框架都是使用的这种技术,例如:ButterKnife,EventBus,ARout,IOC,ROOM等等,应用非常广泛,APT技术一般是结合JavaPoet使用,具体怎么使用,请参照android注解之APT和javapoet_袁震的博客-CSDN博客

3.2.2RetentionPolicy.CLASS

字节码级别,在编译出Class后,通过修改Class数据以实现修改代码逻辑目的。对于是否需要修改的区分或者修改为不同逻辑的判断可以使用注解。

这个最常用的技术就是字节码插桩,字节码插桩框架主要有三种,ASM,Javassist,AspectJ。

性能方面ASM最优。稳定性和简单性方面AspectJ最优。利用AOP思想,我们可以无侵入的实现埋点,监控方法耗时,UI卡顿监控,自动添加日志,代码隔离等等功能。以后有时间会讲解各个功能的实现方案。

3.2.3 RetentionPolicy.RUNTIME

上面两个都属于编译阶段,RetentionPolicy.RUNTIME属于运行时期,它是在程序运行期间,通过反射技术动态获取注解与其元素,从而完成不同的逻辑判定。

这个最典型的使用案例就是反射,关于反射相信大家都不陌生了。如果有不清楚反射的,可以看一下android反射详解这个文章。

3.3@Inherited

该注解的作用是子类可以继承父类的注解,意思就是,如果一个注解添加了@Inherited,例如:

@Inherited
@Target(ElementType.TYPE)
public @interface YuanZhen {
}


那么用该注解修饰的类,其子类可以获取到该注解。

3.4@IntDef 和@StringDef

@IntDef 和@StringDef是安卓引入的新的元注解,这两个注解的主要作用是用来替代枚举的,因为枚举对内存的消耗比较大,下面来看怎么使用:

自定义一个注解:

@Inherited
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.SOURCE)
@IntDef({DataBean.DOCTOR, DataBean.TEACHER})
public @interface YuanZhen {
    
}


数据类:

public class DataBean {
 
    public static final int DOCTOR = 0;
 
    public static final int TEACHER = 1;
 
    private int type;
 
    public void setType(@YuanZhen int type) {
        this.type = type;
    }
 
    public int getType() {
        return type;
    }
}

使用时:

@StringDef和@IntDef的用法一样,这里就不再举例了。

4,注解类型元素

注解允许我们在使用时传递参数,可以传递一个参数或者多个参数

当只有一个参数需要传递时,我们可以这样写:

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface YuanZhen {
 
    String value() default "111";
 
}
@YuanZhen("222")
public void testMethod(){
}
//因为有默认值 也可以这样写
@YuanZhen()
public void testMethod(){
}

注意:在使用注解时,如果定义的注解中的类型元素无默认值,则必须进行传值。

当有多个参数时,可以这样写:

@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface YuanZhen {
 
    String value() default "111";
    
    int age();
 
}
@YuanZhen(value = "222",age = 88)
public void testMethod(){
}

5,总结

关于注解的基本知识就是这些,基本的注解并不难掌握,但是后面想要提高自己的架构能力,就必须结合APT,JavaPoet以及字节码插桩技术和反射,综合运用到框架中去,这并不是一件简单的事情,但是了解之后,我们就会发现一片新的天地。


android注解之APT和javapoet

1,APT是什么?

APT,英文全称Annotation Processing Tool,是一种处理注释的工具,它对源代码文件进行检测找出其中的Annotation,根据注解自动生成代码, 如果想要自定义的注解处理器能够正常运行,必须要通过APT工具来进行处理。 也可以这样理解,只有通过声明APT工具后,程序在编译期间自定义注解解释器才能执行。注意,是在编译期间。

简单来讲,就是根据我们定义的注释规则,帮助我们生成代码,生成类文件。

2,APT中的元素

在APT中,它会分为包元素,类元素,属性元素,方法元素。那么,这些元素的意义是什么呢?

 首先,我们需要明白APT真正的作用是什么。就拿ButterKnife来说,他真正要实现的就是我们通过BindView,把id传给注解,然后就会在编译时动态生成很多类,专门去处理你绑定的这些id,从而达到你只需要几行代码就能实现绑定,点击事件等功能。那为什么不能直接就写好类去处理,而非要到编译期去自动生成类处理呢?因为它不知道你会传哪些id,所以需要动态的生成。

所以,我认为APT的主要作用就是帮你动态生成类。

import androidx.appcompat.app.AppCompatActivity;//PackageElement 包元素/节点
 
public class MainActivity2 extends AppCompatActivity { // TypeElement 类元素/节点
 
    private int a;// VariableElement 属性元素/节点
 
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {// ExecuteableElement 方法元素/节点
        super.onCreate(savedInstanceState);
    }
}


这些元素的意义就是,它们会提供相关信息,来帮助你后面生成类。

3,APT中常用的API

在AbstractProcessor中,有两个方法是核心方法:

    
//初始化工作,主要做一些准备工作
public synchronized void init(ProcessingEnvironment processingEnv) {
    
}
 
//处理注解 核心方法
//annotations 使用了支持处理注解的节点集合
//roundEnv 当前或是之前的运行环境,可以通过该对象查找的注解
//return true 表示后续处理器不会再处理(已经处理完成)
public abstract boolean process(Set<? extends TypeElement> annotations,
                                    RoundEnvironment roundEnv);

上面ProcessingEnvironment中常用的api如下:

上面RoundEnvironment常用api如下:

//获取所有被@YuanZhen注解的元素集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(YuanZhen.class);

Element常用api如下:

4,APT环境搭建

4.1新建注解javalib,命名为compiler

4.2在compiler的buidl.gradle中添加依赖,用来注册注解处理器
compileOnly'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
4.3新建AnnotationProcessor

新建MyAnnotationProcessor类,继承AnnotationProcessor

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
 
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({"com.yuanzhen.yuanzhenannotation.YuanZhen"})
 
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {
 
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }
 
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment       roundEnvironment) {
        
        return false;
    }
}
4.4新建注解javalib  命名为yuanzhenannotation

4.5创建自己的注解
@Target(ElementType.TYPE) //作用与类上
@Retention(RetentionPolicy.SOURCE) //在编译时期生效
public @interface YuanZhen {
 
    String value();//一个默认值
}

4.6添加依赖

在compiler中依赖yuanzhenannotation

在app中添加依赖:

4.7在app中使用注解:
@YuanZhen("study")
public class MainActivity extends AppCompatActivity {
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

至此,APT环境搭建完成

5,APT获取注解

在MyAnnotationProcessor类中:

// AutoService则是固定的写法,加个注解即可
// 通过auto-service中的@AutoService可以自动生成AutoService注解处理器,用来注册
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
 
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({"com.yuanzhen.yuanzhenannotation.YuanZhen"})
 
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class MyAnnotationProcessor extends AbstractProcessor {
 
    private Messager messager;// 用来打印日志相关信息
    private Elements elementUtils;// 操作Element的工具类(类,函数,属性,其实都是Element)
    private Filer filer;//文件生成器, 类 资源 等,就是最终要生成的文件 是需要Filer来完成的
    private Types typeUtils;// type(类信息)的工具类,包含用于操作TypeMirror的工具方法
 
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        messager = processingEnv.getMessager();
        elementUtils = processingEnv.getElementUtils();
        filer = processingEnv.getFiler();
        typeUtils = processingEnv.getTypeUtils();
    }
 
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //因为javalib没有Log,所以我们使用messager来打印
        messager.printMessage(Diagnostic.Kind.NOTE,"aaaaaaZZZZZ");//打印 
        //获取所有被@YuanZhen注解的元素集合
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(YuanZhen.class);
        for (Element element : elements) {
            String className =element.getSimpleName().toString();//获取元素名
            messager.printMessage(Diagnostic.Kind.NOTE,"-----="+className);//打印类名
            YuanZhen annotation = element.getAnnotation(YuanZhen.class);//获取注解
            messager.printMessage(Diagnostic.Kind.NOTE,"-----value="+annotation.value());//打印注解参数
        }
        return true;
    }
}

具体的api在上文已经有个介绍,下面看Build日志输出:

参数study已经传递过来,类名MainActivity也已经获取到。

6,传统的生成类的方式

上面已经完成了APT环境的配置,下面就是动态生成类了,在javapoet之前,传统的生成类的方式就是采用字符串拼接的样式。最典型的应用就是EventBus。

private void createInfoIndexFile(String index) {
    BufferedWriter writer = null;
    try {
    	// 通过注解处理的文件操作工具类创建源文件
        JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
        int period = index.lastIndexOf('.');
        // 截取包名和类名
        String myPackage = period > 0 ? index.substring(0, period) : null;
        String clazz = index.substring(period + 1);
        writer = new BufferedWriter(sourceFile.openWriter());
        // 以下就是写入生成的源文件中的代码
        if (myPackage != null) {
            writer.write("package " + myPackage + ";\n\n");
        }
        writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
        writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
        writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
        writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
        writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
        writer.write("import java.util.HashMap;\n");
        writer.write("import java.util.Map;\n\n");
        writer.write("/** This class is generated by EventBus, do not edit. */\n");
        writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
        writer.write("    private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
        writer.write("    static {\n");
        writer.write("        SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
        // 写入订阅方法相关信息
        writeIndexLines(writer, myPackage);
        writer.write("    }\n\n");
        writer.write("    private static void putIndex(SubscriberInfo info) {\n");
        writer.write("        SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
        writer.write("    }\n\n");
        writer.write("    @Override\n");
        writer.write("    public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
        writer.write("        SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
        writer.write("        if (info != null) {\n");
        writer.write("            return info;\n");
        writer.write("        } else {\n");
        writer.write("            return null;\n");
        writer.write("        }\n");
        writer.write("    }\n");
        writer.write("}\n");
    } catch (IOException e) {
        throw new RuntimeException("Could not write source for " + index, e);
    } finally {
        if (writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                //Silent
            }
        }
    }
}

通过上面可以看到,Eventbus是通过字符串拼接的形式来生成类的,这种方式虽然比较简单,但是也不是一件轻松的体力活,不符合OOP的编程思想。下面我们就来介绍一下现在最流行的写法,采用javapoet。

7,JavaPoet简介

JavaPoet是square推出的开源java代码生成框架,提供Java Api生成.java源文件,这个框架功能非常实用,也是我们习惯的Java面向对象OOP语法, 可以很方便的使用它根据注解生成对应代码。 通过这种自动化生成代码的方式, 可以让我们用更加简洁优雅的方式要替代繁琐冗杂的重复工作。

8,JavaPoet中常用的类

9,JavaPoet生成类的思想

传统模式生成类的思想是先写包,然后类,然后方法。

JavaPoet的思想恰恰相反,它是先方法,然后类,然后包。

10,JavaPoet 使用

10.1 导包

在complier的build.gradle里面导包

 implementation "com.squareup:javapoet:1.9.0"

10.2写出要生成的class
/**
package com.yuanzhen.yuanzhenannotation;
public class MyClass {
    public static void main(String[] args) {
        System.out.println("Hello, yuanzhen");
    }
}
*/

10.3 通过javapoet生成该类
 // 1.方法 
MethodSpec mainMethod = MethodSpec.methodBuilder("main")//添加方法名
                .addModifiers(Modifier.PUBLIC, Modifier.STATIC)//添加修饰符
                .returns(void.class)//添加返回值
                .addParameter(String[].class, "args")//添加方法参数
                .addStatement("$T.out.println($S)", System.class, "Hello, YuanZhen!")//添加内容
                .build();
        // 2.类
        TypeSpec helloWorld = TypeSpec.classBuilder("MyClass1")//添加类名
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL)//添加修饰符
                .addMethod(mainMethod)//添加方法
                .build();
        // 3.包
        JavaFile packagef = JavaFile.builder("com.yuanzhen.apt1", helloWorld).build();
        // 去生成
        try {
            packagef.writeTo(filer);
            messager.printMessage(Diagnostic.Kind.NOTE, "success...");
        } catch (IOException e) {
            e.printStackTrace();
            messager.printMessage(Diagnostic.Kind.NOTE, "error...");
        }

10.4在app build中查看生成的文件

11,总结

关于APT和JavaPoet的使用就讲完了,利用这个技术,我们可以实现很多强大的功能。


Android JavaOpet

Android/Java 框架常常会使用编译期注解, 而其中关键的一步是如何生成 Java 文件. JavaPoet 就是用来生成 Java 代码的一个 Java Library.

基础概念

首先, 让我们来看一个简单的 Java 文件:

可以见, 一个 Java 文件由四部分组成。

也就是我们要生成一个 java 文件, 就是要生成上述四部分内容。

对应到 JavaPoet 中, 我们来看一张对比图:

可见, JavaFile 对应的 Java 文件。 包名直接用一个字符串表示。 TypeSpec 表示一个 class 定义。 FieldSpec 表示类中的属性。 MethodSpec 表示类中的方法。

最简实例

理解了 JavaPoet 的大致结构, 我们就可以生成一个简单的 Java 文件了。

gradle 引用:

compile 'com.squareup:javapoet:1.8.0'
public class HowToJavaPoetDemo {
  public static void main(String[] args) {
    // `JavaFile` 代表 Java 文件
    JavaFile javaFile = JavaFile.builder("com.walfud.howtojavapoet",
      // TypeSpec 代表一个类
      TypeSpec.classBuilder("Clazz")
              // 给类添加一个属性
              .addField(FieldSpec.builder(int.class, "mField", Modifier.PRIVATE)
                                 .build())
              // 给类添加一个方法
              .addMethod(MethodSpec.methodBuilder("method")
                    .addModifiers(Modifier.PUBLIC)
                    .returns(void.class)
                    .addStatement("System.out.println(str)")
                    .build())
              .build())
      .build();

    System.out.println(javaFile.toString());
  }
}

更多的数据类型

上个例子中, 我们为了单一目标, 特意简化了数据类型。 现实编程中绝大多数情况下要面对各种对象类型, 如 File / List / Activity(android.app.Activity) 等等。 下面我们来了解一下 JavaPoet 的类型系统。

我们要定义一个 java.io.File 类型的变量, 比如这样:

import java.io.File;

public class ... {
    private File mFile;
}

我们需要使用 FieldSpec.builder(File.class, "mFile", Modifier.PRIVATE).build() 来定义这个属性。 这样 JavaPoet 就可以自动找到 File 所在的包并帮我们自动引入 import java.io.File

但如果定义 List<String> 这种泛型的话, 该怎么写呢?List<String>.class 显然是不行的。 因为这是一个复合类型, 其中的参数类型无法被表示。

因此, JavaPoet 帮我们定义了如下几种专门描述类型的类。其关系图如下:

TypeSpec————用于生成类、接口、枚举对象的类
MethodSpec————用于生成方法对象的类
ParameterSpec————用于生成参数对象的类
AnnotationSpec————用于生成注解对象的类
FieldSpec————用于配置生成成员变量的类
ClassName————通过包名和类名生成的对象,在JavaPoet中相当于为其指定Class
ParameterizedTypeName————通过MainClass和IncludeClass生成包含泛型的Class
JavaFile————控制生成的Java文件的输出的类

我们举几个常见的例子:

分类生成的类型JavaPoet 写法也可以这么写 (等效的 Java 写法)
内置类型intTypeName.INTint.class
数组类型int[]ArrayTypeName.of(int.class)int[].class
需要引入包名的类型java.io.FileClassName.get(“java.io”, “File”)java.io.File.class
参数化类型 (ParameterizedType)ListParameterizedTypeName.get(List.class, String.class)
类型变量 (WildcardType)用于声明泛型TTypeVariableName.get(“T”)
通配符类型? extends StringWildcardTypeName.subtypeOf(String.class)

这些类型之间可以相互嵌套, 比如 ParameterizedTypeName.get(List.class, String.class) 其中 List.class 等价于 ClassName.get("java.util", "List")。 因此,
ParameterizedTypeName.get(List.class, String.class)
可以写为
ParameterizedTypeName.get(ClassName.get("java.util", "List"), ClassName.get("java.lang", "String"))
 
前者的好处是简洁, 后者的好处是 “使用 ClassName 代表某个类型而无需引入该类型“。 比如: 由于在 java 工程中是没有 android 的 sdk, 所以你在 java 工程中想生成 android.app.Activity 这种类型是不能直接 Activity.class。这种情况下只能通过 ClassName 进行引用。”
 

在 Statement 中使用类型

上面介绍了 JavaPoet 的类型系统, 现在我们来看看最后一道坎: statemenet 中的类型占位符。
 
我们先来看看 statement 占位符在哪里使用:

MethodSpec.methodBuilder("method")
    .addStatement("$T file", File.class)            // File file;
    .addStatement("$L = null", "file")              // file = null;
    .addStatement("file = new File($S)", "foo/bar") // file = new File("foo/bar");
    .build();

 
$T 是类型替换, 一般用于 ("$T foo", List.class) => List foo. $T 的好处在于 JavaPoet 会自动帮你补全文件开头的 import. 如果直接写 ("List foo") 虽然也能生成 List foo, 但是最终的 java 文件就不会自动帮你添加 import java.util.List.
$L 是字面量替换, 比如 ("abc$L123", "FOO") => abcFOO123. 也就是直接替换.
$S 是字符串替换, 比如: ("$S.length()", "foo") => "foo".length() 注意 $S 是将参数替换为了一个带双引号的字符串. 免去了手写 "\"foo\".length()" 中转义 (\") 的麻烦.
$N 是名称替换, 比如你之前定义了一个函数 MethodSpec methodSpec = MethodSpec.methodBuilder("foo").build(); 现在你可以通过 $N 获取这个函数的名称 ("$N", methodSpec) => foo.
 

万能例子

最后, 我们展示一段几乎涵盖你所常见 case 的例子 (仅为了展示 JavaPoet 用法, 生成的代码可能编译不过). 如果哪里不知道怎么生成的, 可以方便的在下面的生成代码里查找生成方法.

package com.walfud.howtojavapoet;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.lang.Comparable;
import java.lang.Exception;
import java.lang.Integer;
import java.lang.Object;
import java.lang.Override;
import java.lang.Runnable;
import java.lang.RuntimeException;
import java.lang.String;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class Clazz<T> extends String implements Serializable, Comparable<String>, Map<T, ? extends String> {
  static {
  }

  private int mInt;

  private int[] mArr;

  private File mRef;

  private T mT;

  private List<String> mParameterizedField;

  private List<? extends String> mWildcardField;

  {
  }

  public Clazz() {
  }

  @Override
  public <T> int method(String string, T t, Map<Integer, ? extends T> map) throws IOException,
      RuntimeException {
    int foo = 1;
    String bar = "a string";
    Object obj = new HashMap<Integer, ? extends T>(5);
    baz(new Runnable(String param) {
      @Override
      void run() {
      }
    });
    for (int i = 0; i < 5; i++) {
    }
    while (false) {
    }
    do {
    } while (false);
    if (false) {
    } else if (false) {
    } else {
    }
    try {
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
    }
    return 0;
  }

  class InnerClass {
  }
}

JavaPoet 代码:

package com.walfud.howtojavapoet;


import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.lang.model.element.Modifier;

/**
 * http://android.walfud.com/javapoet-看这一篇就够了/
 */
class HowToJavaPoetDemo {
    public static void main(String[] args) {
        TypeSpec clazz = clazz(builtinTypeField(),          // int
                               arrayTypeField(),            // int[]
                               refTypeField(),              // File
                               typeField(),                 // T
                               parameterizedTypeField(),    // List<String>
                               wildcardTypeField(),         // List<? extends String>
                               constructor(),               // 构造函数
                               method(code()));             // 普通方法
        JavaFile javaFile = JavaFile.builder("com.walfud.howtojavapoet", clazz).build();

        System.out.println(javaFile.toString());
    }

    /**
     * `public abstract class Clazz<T> extends String implements Serializable, Comparable<String>, Comparable<? extends String> {
     * ...
     * }`
     *
     * @return
     */
    public static TypeSpec clazz(FieldSpec builtinTypeField, FieldSpec arrayTypeField, FieldSpec refTypeField,
                                 FieldSpec typeField, FieldSpec parameterizedTypeField, FieldSpec wildcardTypeField,
                                 MethodSpec constructor, MethodSpec methodSpec) {
        return TypeSpec.classBuilder("Clazz")
                    // 限定符
                    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
                    // 泛型
                    .addTypeVariable(TypeVariableName.get("T"))

                    // 继承与接口
                    .superclass(String.class)
                    .addSuperinterface(Serializable.class)
                    .addSuperinterface(ParameterizedTypeName.get(Comparable.class, String.class))
                    .addSuperinterface(ParameterizedTypeName.get(ClassName.get(Map.class), 
                                                                 TypeVariableName.get("T"), 
                                                                 WildcardTypeName.subtypeOf(String.class)))

                    // 初始化块
                    .addStaticBlock(CodeBlock.builder().build())
                    .addInitializerBlock(CodeBlock.builder().build())

                    // 属性
                    .addField(builtinTypeField)
                    .addField(arrayTypeField)
                    .addField(refTypeField)
                    .addField(typeField)
                    .addField(parameterizedTypeField)
                    .addField(wildcardTypeField)

                    // 方法 (构造函数也在此定义)
                    .addMethod(constructor)
                    .addMethod(methodSpec)

                    // 内部类
                    .addType(TypeSpec.classBuilder("InnerClass").build())

                    .build();
    }

    /**
     * 内置类型
     */
    public static FieldSpec builtinTypeField() {
        // private int mInt;
        return FieldSpec.builder(int.class, "mInt", Modifier.PRIVATE).build();
    }

    /**
     * 数组类型
     */
    public static FieldSpec arrayTypeField() {
        // private int[] mArr;
        return FieldSpec.builder(int[].class, "mArr", Modifier.PRIVATE).build();
    }

    /**
     * 需要导入 import 的类型
     */
    public static FieldSpec refTypeField() {
        // private File mRef;
        return FieldSpec.builder(File.class, "mRef", Modifier.PRIVATE).build();
    }

    /**
     * 泛型
     */
    public static FieldSpec typeField() {
        // private File mT;
        return FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
    }

    /**
     * 参数化类型
     */
    public static FieldSpec parameterizedTypeField() {
        // private List<String> mParameterizedField;
        return FieldSpec.builder(ParameterizedTypeName.get(List.class, String.class),
                                 "mParameterizedField",
                                 Modifier.PRIVATE)
                .build();
    }

    /**
     * 通配符参数化类型
     *
     * @return
     */
    public static FieldSpec wildcardTypeField() {
        // private List<? extends String> mWildcardField;
        return FieldSpec.builder(ParameterizedTypeName.get(ClassName.get(List.class),
                                                           WildcardTypeName.subtypeOf(String.class)),
                                 "mWildcardField",
                                 Modifier.PRIVATE)
                .build();
    }

    /**
     * 构造函数
     */
    public static MethodSpec constructor() {
        return MethodSpec.constructorBuilder()
                .addModifiers(Modifier.PUBLIC)
                .build();
    }

    /**
     * `@Override
     * public <T> Integer method(String string, T t, Map<Integer, ? extends T> map) throws IOException, RuntimeException {
     * ...
     * }`
     *
     * @param codeBlock
     * @return
     */
    public static MethodSpec method(CodeBlock codeBlock) {
        return MethodSpec.methodBuilder("method")
                .addAnnotation(Override.class)
                .addTypeVariable(TypeVariableName.get("T"))
                .addModifiers(Modifier.PUBLIC)
                .returns(int.class)
                .addParameter(String.class, "string")
                .addParameter(TypeVariableName.get("T"), "t")
                .addParameter(ParameterizedTypeName.get(ClassName.get(Map.class), 
                                                        ClassName.get(Integer.class), 
                                                        WildcardTypeName.subtypeOf(TypeVariableName.get("T"))), 
                              "map")
                .addException(IOException.class)
                .addException(RuntimeException.class)
                .addCode(codeBlock)
                .build();
    }

    /**
     * ‘method’ 方法中的具体语句
     */
    public static CodeBlock code() {
        return CodeBlock.builder()
                .addStatement("int foo = 1")
                .addStatement("$T bar = $S", String.class, "a string")

                // Object obj = new HashMap<Integer, ? extends T>(5);
                .addStatement("$T obj = new $T(5)", 
                              Object.class, ParameterizedTypeName.get(ClassName.get(HashMap.class), 
                                                                      ClassName.get(Integer.class), 
                                                                      WildcardTypeName.subtypeOf(TypeVariableName.get("T"))))

                // method(new Runnable(String param) {
                //   @Override
                //   void run() {
                //   }
                // });
                .addStatement("baz($L)", TypeSpec.anonymousClassBuilder("$T param", String.class)
                        .superclass(Runnable.class)
                        .addMethod(MethodSpec.methodBuilder("run")
                                .addAnnotation(Override.class)
                                .returns(TypeName.VOID)
                                .build())
                        .build())

                // for
                .beginControlFlow("for (int i = 0; i < 5; i++)")
                .endControlFlow()

                // while
                .beginControlFlow("while (false)")
                .endControlFlow()

                // do... while
                .beginControlFlow("do")
                .endControlFlow("while (false)")

                // if... else if... else...
                .beginControlFlow("if (false)")
                .nextControlFlow("else if (false)")
                .nextControlFlow("else")
                .endControlFlow()

                // try... catch... finally
                .beginControlFlow("try")
                .nextControlFlow("catch ($T e)", Exception.class)
                .addStatement("e.printStackTrace()")
                .nextControlFlow("finally")
                .endControlFlow()

                .addStatement("return 0")
                .build();
    }
}

为了方便记忆, 我总结了一张图, 标注了关键或者稍微复杂的那些用法:

注意: 在 Android 工程中, 是无法使用 `javax.lang.model.element.Modifier` 包的, 因为 Android 运行时没有相关的 class. 对于 Android Annotation 的正确姿势(工程结构以及用法), 请参考: Java 注解技术


JavaPoet使用详解

JavaPoet 是用于生成 .java 源文件的 Java API。

在执行诸如注释处理或与元数据文件(例如,数据库模式、协议格式)交互等操作时,源文件生成非常有用。通过生成代码,您无需编写样板,同时还为元数据保留了单一的真实来源。

例子

这是一个(无聊的)HelloWorld 类:

package com.example.helloworld;

public final class HelloWorld {
  public static void main(String[] args) {
    System.out.println("Hello, JavaPoet!");
  }
}

这是使用 JavaPoet 生成它的(令人兴奋的)代码:

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);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

为了声明 main 方法,我们创建了一个 MethodSpec “main”,配置了修饰符、返回类型、参数和代码语句。我们将 main 方法添加到 HelloWorld 类中,然后将其添加到 HelloWorld.java 文件中。

在这种情况下,我们将文件写入 System.out,但我们也可以将其作为字符串获取 (JavaFile.toString()) 或将其写入文件系统 (JavaFile.writeTo())

Javadoc 对完整的 JavaPoet API 进行了编目,我们将在下面进行探讨。

1.添加依赖

dependencies {
    implementation 'com.squareup:javapoet:1.13.0'
}
  • 1
  • 2
  • 3

2.使用方法

生成java文件,需要的基本元素包括:

  • 创建字段(属性)
  • 创建方法
  • 创建类(匿名类)、接口或枚举
  • 输出文件

接下来一一介绍这些用法。

FieldSpec(字段)

可以使用构建器或使用方便的辅助方法来创建字段,FieldSpec就是用来创建字段的类

FieldSpec.builder(String.class, "android").build()
  • 1

以上代码等价于String android;

如果添加修饰符可以使用addModifiers

FieldSpec.builder(String.class, "android")
	.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
	.build()
// private final String android;
  • 1
  • 2
  • 3
  • 4

其中Modifier包括我们用到的所有修饰符,下面介绍的类,方法都是使用它:

public enum Modifier {
    /** The modifier {@code public} */          PUBLIC,
    /** The modifier {@code protected} */       PROTECTED,
    /** The modifier {@code private} */         PRIVATE,
    /** The modifier {@code abstract} */        ABSTRACT,
    /**
     * The modifier {@code default}
     * @since 1.8
     */
     DEFAULT,
    /** The modifier {@code static} */          STATIC,
    /** The modifier {@code final} */            FINAL,
    /** The modifier {@code transient} */       TRANSIENT,
    /** The modifier {@code volatile} */        VOLATILE,
    /** The modifier {@code synchronized} */    SYNCHRONIZED,
    /** The modifier {@code native} */          NATIVE,
    /** The modifier {@code strictfp} */        STRICTFP;
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

当字段具有 Javadoc、注释或字段初始值设定项时,扩展形式是必需的。字段初始值设定项使用 -like 语法,如果需要初始化参数,使用initializer

FieldSpec.builder(String.class, "android")
	.addModifiers(Modifier.PRIVATE, Modifier.FINAL)
	.initializer("$S", "Android")
	.build()
// private final String android = "Android";


FieldSpec android = FieldSpec.builder(String.class, "android")
    .addModifiers(Modifier.PRIVATE, Modifier.FINAL)
    .initializer("$S + $L", "Lollipop v.", 5.0d)
    .build();
//private final String android = "Lollipop v." + 5.0;

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

MethodSpec(方法)

MethodSpec用来创建方法,一个最简单的方法包含方法名、返回类型。

MethodSpec.methodBuilder("test")
	.returns(void.class)
	.build()
// 	void test() {}
  • 1
  • 2
  • 3
  • 4

添加修饰符同上,这里不重复说明,如果添加方法参数,需要使用addParameter

MethodSpec.methodBuilder("test")
	.addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "str")
	.returns(void.class)
	.build()
	
/*public void test(String str) {
}*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

最后就是给方法添加语句,需要使用addStatement:

MethodSpec.methodBuilder("test")
	.addModifiers(Modifier.PUBLIC)
    .addParameter(String.class, "str")
    .addStatement("System.out.println(str)")
	.returns(void.class)
	.build()
	
/*public void test(String str) {
	System.out.println(str);
}*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

以上所有方法都有一个代码体。用于获取没有任何主体的方法。这仅在封闭类是抽象类或接口时才合法。

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

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addMethod(flux)
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

产生这个:

public abstract class HelloWorld {
  protected abstract void flux();
}
  • 1
  • 2
  • 3

泛型

FieldSpec.builder(TypeVariableName.get("T"), "mT", Modifier.PRIVATE).build();
// private T mT;

TypeVariableName mTypeVariable = TypeVariableName.get("T");
ParameterizedTypeName mListTypeName = ParameterizedTypeName.get(ClassName.get(List.class), mTypeVariable);
FieldSpec fieldSpec = FieldSpec.builder(mListTypeName, "mList", Modifier.PRIVATE).build();

//private List<T> mList;
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

方法和类中使用addTypeVariable添加泛型。

初始化块

TypeSpec.classBuilder("Test")
    .addStaticBlock(CodeBlock.builder().build())
    .addInitializerBlock(CodeBlock.builder().build())
    .build();
    
/*
class Test {
    static {
    }

    {
    }
}*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

addCode和ControlFlow(代码和控制流)

JavaPoet 使用字符串作为代码块:

MethodSpec.methodBuilder("main")
    .addCode(""
        + "int total = 0;\n"
        + "for (int i = 0; i < 10; i++) {\n"
        + "  total += i;\n"
        + "}\n")
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

产生这个:

void main() {
  int total = 0;
  for (int i = 0; i < 10; i++) {
    total += i;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

可以看到使用addCode方法,可以一股脑的添加所有代码,但是我们需要自己换行,输入分号和缩进。

手动分号、换行和缩进很乏味,因此 JavaPoet 提供了 API 以使其更容易。有分号和换行符,+ 一起用于大括号、换行符和缩进,
使用addStatement 可以帮我们添加分号和换行,而使用beginControlFlow 和 endControlFlow组合可以帮我们轻松实现控制流代码。所以上面的代码等价于:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("int total = 0")
    .beginControlFlow("for (int i = 0; i < 10; i++)")
    .addStatement("total += i")
    .endControlFlow()
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

这个例子很蹩脚,因为生成的代码是常量!假设我们不只是将 0 加到 10,而是希望操作和范围可配置。这是一个生成方法的方法:

private MethodSpec computeRange(String name, int from, int to, String op) {
  return MethodSpec.methodBuilder(name)
      .returns(int.class)
      .addStatement("int result = 1")
      .beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
      .addStatement("result = result " + op + " i")
      .endControlFlow()
      .addStatement("return result")
      .build();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

这是我们调用时得到的:

int multiply10to20() {
  int result = 1;
  for (int i = 10; i < 20; i++) {
    result = result * i;
  }
  return result;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

方法生成方法!由于 JavaPoet 生成源代码而不是字节码,您可以通读它以确保它是正确的。

某些控制流语句,例如 ,可以具有无限的控制流可能性。您可以使用以下方法处理这些选项:

MethodSpec main = MethodSpec.methodBuilder("main")
    .addStatement("long now = $T.currentTimeMillis()", System.class)
    .beginControlFlow("if ($T.currentTimeMillis() < now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time travelling, woo hoo!")
    .nextControlFlow("else if ($T.currentTimeMillis() == now)", System.class)
    .addStatement("$T.out.println($S)", System.class, "Time stood still!")
    .nextControlFlow("else")
    .addStatement("$T.out.println($S)", System.class, "Ok, time still moving forward")
    .endControlFlow()
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

产生:

void main() {
  long now = System.currentTimeMillis();
  if (System.currentTimeMillis() < now)  {
    System.out.println("Time travelling, woo hoo!");
  } else if (System.currentTimeMillis() == now) {
    System.out.println("Time stood still!");
  } else {
    System.out.println("Ok, time still moving forward");
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

使用捕获异常也是一个用例:

MethodSpec main = MethodSpec.methodBuilder("main")
    .beginControlFlow("try")
    .addStatement("throw new Exception($S)", "Failed")
    .nextControlFlow("catch ($T e)", Exception.class)
    .addStatement("throw new $T(e)", RuntimeException.class)
    .endControlFlow()
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

产生:

void main() {
  try {
    throw new Exception("Failed");
  } catch (Exception e) {
    throw new RuntimeException(e);
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

其他的使用场景,我举一些例子,大家一看便知:

// do... while
.beginControlFlow("do")
.endControlFlow("while (true)")

// if... else if... else...
.beginControlFlow("if (true)")
.nextControlFlow("else if (false)")
.nextControlFlow("else")
.endControlFlow()

// try... catch... finally
.beginControlFlow("try")
.nextControlFlow("catch ($T e)", Exception.class)
.addStatement("e.printStackTrace()")
.nextControlFlow("finally")
.endControlFlow()

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

占位符

上面的例子中,我们的代码都是固定的字符串,显得不灵活。因此JavaPoet为我们提供了多种占位符来满足要求。

$S (String)

当代码中包含字符串的时候, 可以使用 $S 表示。

private static MethodSpec whatsMyName(String name) {
  return MethodSpec.methodBuilder(name)
      .returns(String.class)
      .addStatement("return $S", name)
      .build();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
$L (Literal)

$L 是字面量替换,它与 $S相似,但是它并不需要转义,也就是不包含字符串的引号。

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();
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
$T (Type)

上面例子为了简单,都使用的是一些基础类型,为的是不需要导包。实际中我们需要使用大量对象,如果只是在字符串中写死,代码虽没有问题,但是没有导包还是会保错。这是可以考虑使用$T,它的作用是替换类型。

MethodSpec today = MethodSpec.methodBuilder("today")
    .returns(Date.class)
    .addStatement("return new $T()", Date.class)
    .build();

TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
    .addMethod(today)
    .build();

JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
    .build();

javaFile.writeTo(System.out);

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

这会生成以下文件,并包含必要的:

package com.example.helloworld;

import java.util.Date;

public final class HelloWorld {
  Date today() {
    return new Date();
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
导入静态

JavaPoet 支持 .它通过显式收集类型成员名称来实现。让我们用一些静态糖来增强前面的例子:

...
ClassName namedBoards = ClassName.get("com.mattel", "Hoverboard", "Boards");

MethodSpec beyond = MethodSpec.methodBuilder("beyond")
    .returns(listOfHoverboards)
    .addStatement("$T result = new $T<>()", listOfHoverboards, arrayList)
    .addStatement("result.add($T.createNimbus(2000))", hoverboard)
    .addStatement("result.add($T.createNimbus(\"2001\"))", hoverboard)
    .addStatement("result.add($T.createNimbus($T.THUNDERBOLT))", hoverboard, namedBoards)
    .addStatement("$T.sort(result)", Collections.class)
    .addStatement("return result.isEmpty() ? $T.emptyList() : result", Collections.class)
    .build();

TypeSpec hello = TypeSpec.classBuilder("HelloWorld")
    .addMethod(beyond)
    .build();

JavaFile.builder("com.example.helloworld", hello)
    .addStaticImport(hoverboard, "createNimbus")
    .addStaticImport(namedBoards, "*")
    .addStaticImport(Collections.class, "*")
    .build();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

JavaPoet 将首先按照配置将您的块添加到文件中,相应地匹配和处理所有调用,并根据需要导入所有其他类型。

package com.example.helloworld;

import static com.mattel.Hoverboard.Boards.*;
import static com.mattel.Hoverboard.createNimbus;
import static java.util.Collections.*;

import com.mattel.Hoverboard;
import java.util.ArrayList;
import java.util.List;

class HelloWorld {
  List<Hoverboard> beyond() {
    List<Hoverboard> result = new ArrayList<>();
    result.add(createNimbus(2000));
    result.add(createNimbus("2001"));
    result.add(createNimbus(THUNDERBOLT));
    sort(result);
    return result.isEmpty() ? emptyList() : result;
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
$N ( Name)

$N是名称替换。例如我们定义了一个getXXX的方法,

我们调用它时可以使用addStatement(“get$L()”, “XXX”)

这种写法实现,但是每次拼接"get"未免太麻烦了,一个不留心说不定还忘记了。那么使用

addStatement("$N()",methodSpec)就更加方便了。
MethodSpec methodSpec = MethodSpec.methodBuilder("get" + name)
    .returns(String.class)
    .addStatement("return $S", name)
    .build();

MethodSpec.methodBuilder("getValue")
    .returns(String.class)
    .addStatement("return $N()", methodSpec)
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

代码块格式字符串

代码块可以通过几种方式为其占位符指定值。代码块上的每个操作只能使用一种样式。

相对参数

将格式字符串中每个占位符的参数值传递给 .在每个示例中,我们生成代码来表示“我吃了 3 个炸玉米饼”

CodeBlock.builder().add("I ate $L $L", 3, "tacos")
  • 1
位置参数

在格式字符串中的占位符之前放置一个整数索引(从 1 开始)以指定要使用的参数。

CodeBlock.builder().add("I ate $2L $1L", "tacos", 3)
  • 1
命名参数

使用语法 where is the format character 并调用包含格式字符串中所有参数键的映射。参数名称使用 、 、 和 中的字符,并且必须以小写字符开头。

Map<String, Object> map = new LinkedHashMap<>();
map.put("food", "tacos");
map.put("count", 3);
CodeBlock.builder().addNamed("I ate $count:L $food:L", map)
  • 1
  • 2
  • 3
  • 4

构造方法

MethodSpec methodSpec = MethodSpec.constructorBuilder()
    .addModifiers(Modifier.PUBLIC)
    .build();

TypeSpec typeSpec = TypeSpec.classBuilder("Test")
    .addMethod(methodSpec)
    .build();

/*
class Test {
    public Test() {
    }
}*/
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

是一个轻微的用词不当;它也可以用于构造函数:

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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

产生这个:

public class HelloWorld {
  private final String greeting;

  public HelloWorld(String greeting) {
    this.greeting = greeting;
  }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

在大多数情况下,构造函数的工作方式与方法一样。发出代码时,JavaPoet 将在输出文件中的方法之前放置构造函数。

参数

使用 or 方便的 API 声明方法和构造函数的参数:

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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

虽然上面生成和参数的代码不同,但输出是一样的:

void welcomeOverlords(final String android, final String robot) {
}
  • 1
  • 2

当参数有注释(如 )时,扩展形式是必要的。

TypeSpec(类,接口,枚举)

TypeSpec用来创建类,接口,或者枚举。

// class Test {}
TypeSpec.classBuilder("Test").build();

//interface Test {}
TypeSpec.interfaceBuilder("Test").build();

/*
enum Test {
    ONE
}*/
TypeSpec typeSpec = TypeSpec.enumBuilder("Test").addEnumConstant("ONE").build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

继承与接口

TypeSpec.classBuilder("Test")
    .superclass(String.class)
    .addSuperinterface(Serializable.class)
    .build();

//class Test extends String implements Serializable {}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

接口

JavaPoet 对接口没有任何问题。请注意,接口方法必须始终为 ,接口字段必须始终为 。定义接口时,这些修饰符是必需的:

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();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

但是这些修饰符在生成代码时被省略了。这些是默认值,因此我们不需要为了 的利益而包含它们!

public interface HelloWorld {
  String ONLY_THING_THAT_IS_CONSTANT = "change";

  void beep();
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

枚举

用于创建枚举类型,并为每个值:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK")
    .addEnumConstant("SCISSORS")
    .addEnumConstant("PAPER")
    .build();
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

要生成这个:

public enum Roshambo {
  ROCK,

  SCISSORS,

  PAPER
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

支持花式枚举,其中枚举值覆盖方法或调用超类构造函数。这是一个全面的例子:

TypeSpec helloWorld = TypeSpec.enumBuilder("Roshambo")
    .addModifiers(Modifier.PUBLIC)
    .addEnumConstant("ROCK", TypeSpec.anonymousClassBuilder("$S", "fist")
        .addMethod(MethodSpec.methodBuilder("toString")
            .addAnnotation(Override.class)
            .addModifiers(Modifier.PUBLIC)
            .addStatement("return $S", "avalanche!")
            .returns(String.class)
            .build())
        .build())
    .addEnumConstant("SCISSORS", TypeSpec.anonymousClassBuilder("$S", "peace")
        .build())
    .addEnumConstant("PAPER", TypeSpec.anonymousClassBuilder("$S", "flat")
        .build())
    .addField(String.class, "handsign", Modifier.PRIVATE, Modifier.FINAL)
    .addMethod(MethodSpec.constructorBuilder()
        .addParameter(String.class, "handsign")
        .addStatement("this.$N = $N", "handsign", "handsign")
        .build())
    .build();

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

产生这个:

public enum Roshambo {
  ROCK("fist") {
    @Override
    public String toString() {
      return "avalanche!";
    }
  },

  SCISSORS("peace"),

  PAPER("flat");

  private final String handsign;

  Roshambo(String handsign) {
    this.handsign = handsign;
  }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

匿名内部类

在枚举代码中,我们使用了 .匿名内部类也可以在代码块中使用。它们是可以引用的值:

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();
    }
  });
}

定义匿名内部类的一个特别棘手的部分是超类构造函数的参数。在上面的代码中,我们传递了没有参数的空字符串: .要传递不同的参数,请使用 JavaPoet 的代码块语法,并使用逗号分隔参数。

TypeSpec.anonymousClassBuilder("") // <- 也可添加参数
    .superclass(Runnable.class)
    .addMethod(MethodSpec.methodBuilder("run")
    			   .addModifiers(Modifier.PUBLIC)
                   .addAnnotation(Override.class)
                   .returns(TypeName.VOID)
                   .build())
    .build();

/*
new Runnable() {
    @Override
    public void run() {
    }
}*/

Annotations(注解)

简单的注释很容易,添加注解的方法可以直接使用addAnnotation

MethodSpec toString = MethodSpec.methodBuilder("toString")
    .addAnnotation(Override.class)
    .returns(String.class)
    .addModifiers(Modifier.PUBLIC)
    .addStatement("return $S", "Hoverboard")
    .build();

它使用注释生成此方法:

  @Override
  public String toString() {
    return "Hoverboard";
  }

如果需要给注解设置属性,那么需要使用AnnotationSpec :

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();

产生这个:

@HeaderList({
    @Header(name = "Accept", value = "application/json; charset=utf-8"),
    @Header(name = "User-Agent", value = "Square Cash")
})
LogReceipt recordEvent(LogRecord logRecord);

请注意,您可以使用相同的属性名称多次调用以填充该属性的值列表。

Javadoc

字段、方法和类型可以用 Javadoc 记录:

MethodSpec dismiss = MethodSpec.methodBuilder("dismiss")
    .addJavadoc("Hides {@code message} from the caller's history. Other\n"
        + "participants in the conversation will continue to see the\n"
        + "message in their own history unless they also delete it.\n")
    .addJavadoc("\n")
    .addJavadoc("<p>Use {@link #delete($T)} to delete the entire\n"
        + "conversation for all participants.\n", Conversation.class)
    .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
    .addParameter(Message.class, "message")
    .build();

产生这个:

  /**
   * Hides {@code message} from the caller's history. Other
   * participants in the conversation will continue to see the
   * message in their own history unless they also delete it.
   *
   * <p>Use {@link #delete(Conversation)} to delete the entire
   * conversation for all participants.
   */
  void dismiss(Message message);

在 Javadoc 中引用类型以获取自动导入时使用。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值