Javapoet是square開發的一款java程式碼生成器,不同於Asm, Javassist等框架修改生成.class檔案直接操作位元組碼,javapoet直接生成java原始碼
工具/原料
jdk
android studio
javapoet
環境搭建
在專案中新建一個java library module名字為app
在app的build.gradle中新增javapoet依賴[推薦]
compile 'com.squareup:javapoet:1.7.0'
或者你也可以將javapoet的jar包下載下來直接放入app的libs目錄
javapoet的下載地址為
直接點選Download (JAR)即可下載
新增好依賴後重新整理一下專案依賴即可生效,
程式碼編寫
接下來我們要生成一個HelloWorld.java
內容為
package com.example.generate;public class HelloWorld {}
在javapoet中.java檔案對應JavaFile
class類對應TypeSpec
因此建立HelloWorld類的程式碼為
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC) .build();
其中classBuilder傳入類名,
addModifiers傳入修飾語
建立HelloWorld.java的程式碼為
PACKAGE = "com.example.generate"
JavaFile javaFile = JavaFile.builder(PACKAGE, typeSpec).build();
我們將建立的內容列印到控制檯
javaFile.writeTo(System.out);
可以看到輸出的內容為
package com.example.generate;
public class HelloWorld {
}
一般我們生成的程式碼最終會儲存為檔案,
javapoet將生成的程式碼儲存成檔案的方法也是呼叫JavaFile的writeTo方法,
我們直接傳入需要儲存的目錄即可
javaFile.writeTo(new File("app/src/main/java"));
HelloWorld.java按照我們的要求生成了,
現在我們將HelloWorld.java的內容改造一下
在HelloWorld.java中新增Parent抽象類
給HelloWorld類新增final修改語,並實現Serializable介面和Parent類並重寫Parent的getMessage方法
並新增一個名為message的static final 修飾的String型別變數,變數的值為Hello, JavaPoet!
同時實現main方法,並在方法中通過System.out.println列印Hello, JavaPoet!
我們期望生成的程式碼如下
package com.example.generate;import java.io.Serializable;public final class HelloWorld extends Parent implements Serializable{ private static final String message = "Hello, JavaPoet!"; public static void main(String[] args){ System.out.println("Hello, JavaPoet!"); } @Override protected String getMessage() { return message; }}abstract class Parent{ protected abstract String getMessage();}
首先我們生成protected abstract String getMessage();方法
MethodSpec getMessage = MethodSpec.methodBuilder("getMessage") .addModifiers(Modifier.PROTECTED) .addModifiers(Modifier.ABSTRACT) .returns(String.class) .build();
然後生成Parent類並新增getMessage方法
TypeSpec parent = TypeSpec.classBuilder("Parent") .addModifiers(Modifier.ABSTRACT) .addMethod(getMessage) .build();
這樣Parent類的生成程式碼就寫好了
接下來寫HelloWorld的實現程式碼,
首先給HelloWorld新增final修飾,並實現介面和繼承Parent類
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.FINAL) .addSuperinterface(Serializable.class) .build();
接下生成message變數
FieldSpec message = FieldSpec.builder(String.class, "message") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addModifiers(Modifier.FINAL) .initializer("\"Hello, JavaPoet!\"") .build();
然後過載getMessage方法,並新增@Override註解
MethodSpec getMessageOvrerride = MethodSpec.methodBuilder("getMessage") .addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED) .returns(String.class) .addStatement("return message") .addModifiers() .build();
再新增main方法,並在main方法中列印字串
MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("System.out.println(\"Hello, JavaPoet!\")") .build();
最後將生成的main方法,重寫的getMessage, 和message欄位新增到HelloWorld中
TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.FINAL) .superclass(ClassName.get(PACKAGE, parent.name)) .addSuperinterface(Serializable.class) .addType(parent) .addField(message) .addMethod(main) .addMethod(getMessageOvrerride) .build();
編譯執行並檢視生成結果,發現生成的程式碼和預期的有點差別
package com.example.generate;import java.io.Serializable;import java.lang.Override;import java.lang.String;public final class HelloWorld extends Parent implements Serializable { public static final String message = "Hello, JavaPoet!"; public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } @Override protected String getMessage() { return message; } abstract class Parent { protected abstract String getMessage(); }}
生成的程式碼變成了HelloWorld的內部類,這並不是我們期望的結果,
檢視javapoet文件也沒有找到解決方法,通過檢視JavaFile程式碼我們發現只能傳入一個TypeSpec,如果要達到我們期望的結果只有自己重新實現JavaFile。
步驟4中我們發現TypeSpec.Builder下的addType新增的只能是內部類,並不能實現一個.java檔案中並列存在多個類,
這裡給出重寫的JavaFile實現JavaFileFixed, 重寫的類中儲存了多個TypeSpec例項,Builder中添加了兩個額外的addType方法,
JavaFileFixed程式碼請到步驟8給的連結中去檢視
將生成HelloWorld程式碼中的.addType(parent)方法去掉,應為我們不想Parent類成為HelloWorld的內部類,
同時將JavaFile javaFile = JavaFile.builder(PACKAGE, typeSpec).build();
替換為JavaFileFixed javaFile = JavaFileFixed.builder(PACKAGE, typeSpec).addType(parent).build();
新實現的程式碼中builder(PACKAGE, typeSpec),typeSpec將作為頂級類,也是.java檔案的名字
addType(parent)中的parent將會作為typeSpec的並列類,更具java語法一個.java檔案中只能有一個public頂積類,因此parent不能是public的,
以下是修改後的程式碼
MethodSpec getMessage = MethodSpec.methodBuilder("getMessage") .addModifiers(Modifier.PROTECTED) .addModifiers(Modifier.ABSTRACT) .returns(String.class) .build();TypeSpec parent = TypeSpec.classBuilder(ClassName.get(PACKAGE, "Parent")) .addModifiers(Modifier.ABSTRACT) .addMethod(getMessage) .build();FieldSpec message = FieldSpec.builder(String.class, "message") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addModifiers(Modifier.FINAL) .initializer("\"Hello, JavaPoet!\"") .build();MethodSpec getMessageOvrerride = MethodSpec.methodBuilder("getMessage") .addAnnotation(Override.class) .addModifiers(Modifier.PROTECTED) .returns(String.class) .addStatement("return message") .addModifiers() .build();MethodSpec main = MethodSpec.methodBuilder("main") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(void.class) .addParameter(String[].class, "args") .addStatement("System.out.println(\"Hello, JavaPoet!\")") .build();TypeSpec typeSpec = TypeSpec.classBuilder("HelloWorld") .addModifiers(Modifier.PUBLIC) .addModifiers(Modifier.FINAL) .superclass(ClassName.get(PACKAGE, parent.name)) .addSuperinterface(Serializable.class) //.addType(parent) .addField(message) .addMethod(main) .addMethod(getMessageOvrerride) .build();JavaFileFixed javaFile = JavaFileFixed.builder(PACKAGE, typeSpec).addType(parent).build();try { javaFile.writeTo(new File("app/src/main/java"));} catch (IOException e) { e.printStackTrace();}
修改後重新編譯執行檢視結果
package com.example.generate;import java.io.Serializable;import java.lang.Override;import java.lang.String;public final class HelloWorld extends Parent implements Serializable { public static final String message = "Hello, JavaPoet!"; public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } @Override protected String getMessage() { return message; }}abstract class Parent { protected abstract String getMessage();}
與我們預期的效果一致
文章中所有原始碼:https://git.oschina.net/jackyanngo/JavaPoetSample.git
注意事項
如果在android studio檢視生成的程式碼,每次執行後要重新整理一下專案中的目錄,才會看到生成的檔案