JavaPoet开源项目使用
JavaPoet项目可以为我们动态的生成Java文件,这是一个很强大和很动态的方法。我们使用注解的时候假如需要生成新的Java文件就可以通过这个开源项目实现。
项目地址:Javapoet
引入
我们在AndroidStudio中新建一个Java module,声明如下:
apply plugin: 'java'
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.squareup:javapoet:1.8.0'
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"复制代码
这样子我们就引入了javapoet。
使用
例子
我们新建了一个名字为javapoettest的java module,然后就开始使用它。
package com.example;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import java.io.File;
import java.io.IOException;
import javax.lang.model.element.Modifier;
public class MyClass {
public static void main(String args[]) throws IOException {
MyClass instance = new MyClass();
instance.generate();
}
private void generate() throws IOException {
// 定义方法
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!")//使用format
.build();
//生成类
TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")//类名字
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)//类修饰符
.addMethod(main)//添加方法
.build();
//生成一个顶级的java文件描述对象
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
File outputFile = new File("javapoettest/src/main/java");
//输出文件
javaFile.writeTo(outputFile);
}复制代码
运行之后,我们可以看到在同一个包名下面,生成了HelloWorld.java文件。同时输出了该Java文件的源码。
详细
- JavaFile,生成Java文件
- MethodSpec 生成方法,构造器
- TypeSpec 生成类,接口
- FieldSpec 生成属性
- ParameterSpec 生成参数
MethodSpec
- 生成方法使用的是MethodSpec类,他的使用如下:
这样子就能够生成一个名字为sayHi(),参数是空,返回值是void的函数,是:MethodSpec hi = MethodSpec.methodBuilder("sayHi").addCode("String s;\n" + "s=\"java\";\n" + "System.out.println(s);\n").build();复制代码
methodBuilder(String)方法指定方法名称,添加代码可以使用:void sayHi() { String s; s="java"; System.out.println(s); }复制代码
例子是使用了第一种,他没有使用format对应的占位符,只是单纯的format。addCode(String format, Object... args) addCode(CodeBlock codeBlock)复制代码
- addCode方法需要我们自己手动添加分号好换行,我们可以使用另外一个方法实现,就是addStatement,例子:
生成的方法就是:MethodSpec sayHi = MethodSpec.methodBuilder("sayHi") .addStatement("int a = 1") .addStatement("int b =2") .addStatement("int sum=a+b") .addCode("System.out.println(sum);\n") .build();复制代码
void sayHi() { int a = 1; int b =2; int sum=a+b; System.out.println(sum); }复制代码
- 循环
我们使用beginControlFlow开始循环,使用endControlFlow结束循环。在这二者之间是循环体。
输出的方法是:MethodSpec sayHello = MethodSpec.methodBuilder("sayHello") .addStatement("int sum=0") .beginControlFlow("for(int i=0;i<10;i++)") .addStatement("sum+=i") .endControlFlow() .addStatement("System.out.println(sum)") .addModifiers(Modifier.PUBLIC,Modifier.STATIC) .build();复制代码
我们通过addModifiers()添加方法的修饰符,这里是public static的,当然你可以选择其他的。public static void sayHello() { int sum = 0; for (int i = 0; i < 10; i++) { sum += i; } System.out.println(sum); }复制代码
- 返回值
第一种用于指定常用的类型,第二种可以通过反射获取指定的类型,例如returns(TypeName returnType) Builder returns(Type returnType)复制代码
或者MethodSpec say = MethodSpec.methodBuilder("say") .returns(TypeName.BOOLEAN) .addStatement("int a = 0") .addStatement("int b = 1") .addStatement("return a> b") .build();复制代码
MethodSpec say = null; try { say = MethodSpec.methodBuilder("say") .returns(Class.forName("java.io.File")) .addStatement("File newFile = new File(\"\")") .addStatement("return newFile").build(); } catch (ClassNotFoundException e) { e.printStackTrace(); }复制代码
- 占位符format
这里的占位符跟我们使用java时候的String.format()很类似。他有四种占位符
- $L,就是一个字面量,变量值
- $S,代表Strings,可以替换多个String
- $T,导入class,添加类型
- $N,代表名字
例子:
生成方法是:
$L
MethodSpec methodL = MethodSpec.methodBuilder("methodL")
.addStatement("String s1=\"$L\"", s1)
.addStatement("int a = $L", a)
.addModifiers(Modifier.PUBLIC)
.addStatement("return a+s1")
.returns(String.class)
.build();
// 生成方法
public String methodL() {
String s1="hi";
int a = 10;
return a+s1;
}复制代码
$S
String s2 = "hi";
String s3 = "hello";
MethodSpec methodS = MethodSpec.methodBuilder("methodS")
.addStatement("String a=$S,b=$S", s2, s3)
.addModifiers(Modifier.PUBLIC)
.build();
// 生成方法是
public void methodS() {
String a="hi",b="hello";
}复制代码
$T
TypeSpec dog = TypeSpec.classBuilder("Dog")
.addModifiers(Modifier.PUBLIC).build();
JavaFile dogJava = JavaFile.builder("com.example.bean", dog).build();
File dogFile = new File("javapoettest/src/main/java");
dogJava.writeTo(dogFile);
Class dogClass = Class.forName("com.example.bean.Dog");
MethodSpec methodT = MethodSpec.methodBuilder("methodT")
.addModifiers(Modifier.PUBLIC)
.addStatement("$T dog= new $T()", dogClass,dogClass)
.returns(Class.forName("com.example.bean.Dog"))
.addStatement("return dog")
.build();复制代码
这里的例子是我们动态生成了一个dog类,在bean包下,我们需要在HelloWord中引入,所以我们就需要进行导入了。那里使用就可以导入,只是需要导入一次即可。但是第一次的时候运行时不可以的,因为这时候还没有生成Dog类,需要运行第二次就ok了。如有同学之后如何第一次运行就可以导入请评论告知,谢谢。当然这里我们导入一些java自带的类的时候就不用反射直接使用XXX.class这样子就ok了。
静态导入,我们在JavaFile构建的时候就可以添加静态导入,静态导入的好处就是可以直接使用一些被静态导入的属性方法不需要通过class.method()调用。例如:
JavaFile javaFile = JavaFile.builder("com.example", typeSpec)
.addStaticImport(Calendar.class,"*")
.build();复制代码
在HelloWorld中就可以使用Calendar类的所有静态属性和静态方法了。需要注意的是静态导入的方式是
import static packageName.className.staticField/staticMethod;
import static packageName.className.*;复制代码
$N
引用一个已经存在的变量作为参数替换,比如一个已经声明存在的方法。例子如下:
MethodSpec hxDigit = MethodSpec.methodBuilder("hexDigit")
.addParameter(int.class,"i")
.returns(char.class)
.addStatement("return (char)(i<10?i+'0':i-10+'a')")
.build();
MethodSpec byteThHex = MethodSpec.methodBuilder("byteToHex")
.addParameter(int.class, "b")
.returns(String.class)
.addStatement("char[] result = new char[2]")
.addStatement("result[0]=$N((b>>4)%0xf)",hxDigit)
.addStatement("result[1]=$N(b&0xf)",hxDigit)
.addStatement("return new String(result)")
.build();复制代码
就会生成方法:
char hexDigit(int i) {
return (char)(i<10?i+'0':i-10+'a');
}
String byteToHex(int b) {
char[] result = new char[2];
result[0]=hexDigit((b>>4)%0xf);
result[1]=hexDigit(b&0xf);
return new String(result);
}复制代码
- 添加参数
第一个是类型,值可以是class或者是TypeName中的某一个,第二个是参数名称,第三个是参数修饰符,方法参数修饰符一般只有final,或者是没有。.addParameter(String.class,"str",Modifier.FINAL) .addParameter(TypeName.BOOLEAN,"isLogin",Modifier.FINAL)复制代码
- 生成构造方法。
生成构造方法是使用MethodSpec.constructorBuilder()函数,并且不用指定函数名称的。其他的跟调用MethodSpec.methodBuilder(String)差不多。//生成空的构造方法 MethodSpec construct = MethodSpec.constructorBuilder() .build(); MethodSpec construct2 = MethodSpec.constructorBuilder() .addParameter(String.class,"str") .addParameter(TypeName.BOOLEAN,"isShow") .addStatement("init(str,isShow)") .build(); //带参数和初始化的构造方法 MethodSpec init = MethodSpec.methodBuilder("init") .addParameter(String.class,"str") .addParameter(Boolean.class,"isShow") .addStatement("this.str = str") .addStatement("this.isShow = isShow") .build();复制代码
TypeSpec
生成类描述的类。 - 设置类名:
这两个方法去设置类名,比如:Builder classBuilder(String name) Builder classBuilder(ClassName className)复制代码
这样子就可以输出Hi.java类。或者是TypeSpec Hi = TypeSpec.classBuilder("Hi").build(); JavaFile HiJavaFile = JavaFile.builder("com.example", Hi).build(); File HiFile = new File("javapoettest/src/main/java"); HiJavaFile.writeTo(HiFile);复制代码
这样子通过自定获取一个ClassName来生成一个Java类。TypeSpec Hello= TypeSpec.classBuilder(ClassName.get("com.example","Hello")).build(); JavaFile HelloJavaFile = JavaFile.builder("com.example", Hello).build(); File HelloFile = new File("javapoettest/src/main/java"); HelloJavaFile.writeTo(HelloFile);复制代码
- 添加方法。
这些其实上面有提到,就死使用addMethod(MethodSpec)就好,无论是普通还是构造方法,还是静态方法。 - 添加属性
跟MethodSpec添加一个参数类似,可以是使用Class声明或者是使用TypeName。.addField(String.class,"str",Modifier.PRIVATE) .addField(TypeName.BOOLEAN,"isShow",Modifier.PRIVATE)复制代码
生成接口
TypeSpec int MethodSpec interfaceMethod = MethodSpec.methodBuilder("beep") .addModifiers(Modifier.PUBLIC,Modifier.ABSTRACT).build(); TypeSpec interfaceTest = TypeSpec.interfaceBuilder("InterfaceA") .addModifiers(Modifier.PUBLIC,Modifier.PUBLIC) .addMethod(interfaceMethod) .build();复制代码
需要注意的是生成的抽象方法需要手动加上public和abstract,对于Interface中的常量也是要加上public final的。
生成抽象方法:继承接口复写方法
MethodSpec mb = MethodSpec.methodBuilder("beep") .addModifiers(Modifier.PUBLIC).addAnnotation(Override.class).build(); TypeSpec mbClass = TypeSpec.classBuilder("B") .addModifiers(Modifier.PUBLIC) .addMethod(mb) .addSuperinterface(InterfaceA.class) .build();复制代码
跟一般生类一样,只不过是需要添加addSuperinterface方法。复写方法的时候可以添加一个注解Override.class注明。
还可以通过superclass(Type/TypeName)方法添加父类。复写父类的方法跟上面的类似。
FieldSpec
生成属性的描述类
例如:
FieldSpec var = FieldSpec.builder(int.class,"i").build();
FieldSpec var2 = FieldSpec.builder(int.class,"j",Modifier.PUBLIC,Modifier.STATIC,Modifier.FINAL)
.initializer("1").build();复制代码
生成的属性是:
public static final int j = 1;
int i;复制代码
可以使用initializer()方法对变量进行初始化。初始化的时候可以使用占位符替换实现动态。
ParameterSpec
生成参数描述类
ParameterSpec spec = ParameterSpec.builder(String.class, "string", Modifier.FINAL).build();
MethodSpec mb2 = MethodSpec.methodBuilder("beep")
.addModifiers(Modifier.PUBLIC)
.addParameter(spec)
.build();复制代码
最后一个参数是修饰符,可以要或者不要,生成的是
public void beep(final String string) {
}复制代码
其他
square还有另外一个类似的项目 javawriter
文章难免有错误,谢谢指出,大家一起共同进步。