開發工具為Android Studio
一. 使用JavaWriter生成java源文件
- (1) 介紹
JavaWriter是square開源項目javapoet中的一個分支, JavaWriter的整個庫中有一個關鍵的類com.squareup.javawriter.JavaWriter
(一共只有兩個類), 主要用來生成Java源文件, 使用鏈式調用依次構建Java源文件. JavaWriter生成Java源文件的方式比較原始。由于整個庫一共才兩個類, 因此沒有對Java源代碼進行建模, 如class、field、method等沒有對應的數據結構, 只有對應的方法來構建生成這些固定的概念。JavaWriter使用起來比較簡單,整個庫也非常小。官網介紹如下:
JavaWriter
is a utility class which aids in generating Java source files.
Source file generation can useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.
- (2) 使用步驟
- 在Android Studio中創建一個
Java library
Module - 在上一步創建的module的構建腳本
<module>/build.gradle
中添加JavaWriter的依賴, 如下:
- 在Android Studio中創建一個
dependencies {
compile 'com.squareup:javawriter:2.5.1'
}
編寫java源文件生成代碼
(3) 使用案例
import com.squareup.javawriter.JavaWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.EnumSet;
import javax.lang.model.element.Modifier;
public class Demo1 {
public static void main(String[] args) throws IOException {
testJavaWriter();
}
/**
* javawriter的github地址: https://github.com/square/javapoet/tree/javawriter_2
* 使用下面語句引用該庫 (倉庫為jcenter):
* compile 'com.squareup:javapoet:1.7.0'
*
* 使用JavaWriter生成java源文件
* @throws IOException
*/
private static void testJavaWriter() throws IOException {
String packageName = "com.example.javawriter.generate";
String className = "GenerateClass";
File outFile = new File("java-demo/src/main/java/" + packageName.replaceAll("\\.", "/") + "/" + className + ".java");
if(!outFile.getParentFile().exists()) {
outFile.getParentFile().mkdirs();
}
if (!outFile.exists()) {
outFile.createNewFile();
}
System.out.println(outFile.getAbsolutePath());
OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(outFile));
JavaWriter jw = new JavaWriter(writer);
jw.emitPackage(packageName)
.beginType(packageName + "." + className, "class", EnumSet.of(Modifier.PUBLIC, Modifier.FINAL))
.emitField("String", "firstName", EnumSet.of(Modifier.PRIVATE))
.emitField("String", "lastName", EnumSet.of(Modifier.PRIVATE))
.emitJavadoc("Return the person's full name")
.beginMethod("String", "getName", EnumSet.of(Modifier.PUBLIC))
.emitStatement("return firstName + \" - \" + lastName")
.endMethod()
.beginMethod("String", "getFirstName", EnumSet.of(Modifier.PUBLIC))
.emitStatement("return firstName")
.endMethod()
.beginMethod("String", "getLastName", EnumSet.of(Modifier.PUBLIC))
.emitStatement("return lastName") //注意不要使用分號結束return語句
.endMethod()
.endType()
.close();
}
}
運行程序, 生成源文件如下:
二. 使用javapoet生成Java源文件
- (1) 介紹
javapoet是大名鼎鼎的square公司開源的一個項目, github地址: https://github.com/square/javapoet. javapoet要比JavaWriter稍微復雜. 此庫定義了一系列的數據結構來表示Java源文件中某些固定的概念, 如class、interface、annoation、field、method等。javapoet對java源文件的結構進行了建模, 其模型如下:
javapoet官網對其介紹如下:
JavaPoet
JavaPoet is a Java API for generating .java source files.
Source file generation can be useful when doing things such as annotation processing or interacting with metadata files (e.g., database schemas, protocol formats). By generating code, you eliminate the need to write boilerplate while also keeping a single source of truth for the metadata.
- (2) 使用步驟
- 在Android Studio中創建一個
Java Library
Module - 在上一步創建的Module的<module>/build.gradle構建腳本中添加javapoet庫的依賴, 如下:
- 在Android Studio中創建一個
dependencies {
compile 'com.squareup:javapoet:1.7.0'
}
編寫生成Java源文件的代碼
(3) 使用案例
import com.squareup.javapoet.FieldSpec;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
import java.io.File;
import java.io.IOException;
import javax.lang.model.element.Modifier;
/**
* @author stone
* @date 16/9/12
*/
public class Demo2 {
public static void main(String[] args) {
testJavaPoet();
}
/**
* 庫: https://github.com/square/javapoet/
* 使用下面語句引用javapoet (倉庫為jcenter):
* compile 'com.squareup:javawriter:2.5.1'
*
* 使用javapoet生成java源文件的步驟 (1,2,3步驟可以交換):
* 1. 構建成員變量
* 2. 構建構造方法
* 3. 構建方法(static/concrete)
* 4. 構建類型(enum/annotation/interface/class)
* 5. 構建java源文件
* 6. 輸出java源文件到文件系統
*/
private static void testJavaPoet() {
String packageName = "com.stone.demo.javawriter";
String className = "HelloWorld";
//1. 生成一個字段
FieldSpec fieldSpec = FieldSpec.builder(String.class, "var", Modifier.PUBLIC).build();
//2. 生成一個方法 (方式一: 面向代碼, 更為底層的構建方式)
MethodSpec mainMethod = MethodSpec.methodBuilder("main") //設置方法名稱
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) //添加修飾符
.addParameter(String[].class, "args") //添加參數
.returns(TypeName.VOID) //添加返回值
.addStatement("$T.out.println($S)", System.class, "Hello world !") //添加代碼語句 (結束語句的分號不需要, 注意與CodeBlock的區別)
.build();
//2. 生成一個方法 (方式二: 對方法建模, 結構化的構建)
// ParameterSpec parameterSpec = ParameterSpec.builder(String[].class, "args").build(); //構建參數模型
// CodeBlock codeBlock = CodeBlock.of("$T.out.println($S);", System.class, "Hello world"); //構建代碼塊 (語句結束的分號不能少)
// MethodSpec methodSpec = MethodSpec.methodBuilder("main") //設置方法名稱
// .addModifiers(Modifier.PUBLIC, Modifier.STATIC) //添加修飾符
// .returns(TypeName.VOID) //添加返回值
// .addParameter(parameterSpec) //添加方法參數
// .addCode(codeBlock) //添加代碼塊
// .build();
//3. 生成類型(enum/class/annotation/interface)
TypeSpec hellworld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(fieldSpec)
.addMethod(mainMethod)
// .addMethod(methodSpec)
.build();
//4. 構建Java源文件
JavaFile javaFile = JavaFile.builder(packageName, hellworld).build();
//5. 輸出java源文件到文件系統
try {
//輸出到控制臺
// javaFile.writeTo(System.out);
//生成java源文件到AndroidStudio的當前Module中
generateToCurrentAndroidStudioModule(javaFile);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 生成到當前module的源文件目錄下
*
* @param javaFile
* @throws IOException
*/
private static void generateToCurrentAndroidStudioModule(JavaFile javaFile) throws IOException {
String targetDirectory = "java-demo/src/main/java"; //輸出到和用例程序相同的源碼目錄下
File dir = new File(targetDirectory);
if (!dir.exists()) {
dir.mkdirs();
}
javaFile.writeTo(dir); //JavaFile.write(), 參數為源碼生成目錄(源碼的classpath目錄)
}
}
運行程序, 生成如下源文件:
詳細用法, 參考官網用例:
https://github.com/square/javapoet/tree/master/src/test/java/com/squareup/javapoet
三. 使用codemodel生成Java源文件
- (1) 介紹
官網如是說:
CodeModel project
CodeModel is a Java library for code generators; it provides a way to generate Java programs in a way much nicer than PrintStream.println(). This project is a spin-off from the JAXB RI for its schema compiler to generate Java source files.
------ From here: https://codemodel.java.net/
IBM Developers是醬紙介紹的:
CodeModel 是用于生成 Java 代碼的 Java 庫,它提供了一種通過 Java 程序來生成 Java 程序的方法。
CodeModel 項目是 JAXB 的子項目。JAXB(Java Architecture for XML Binding)是一項可以根據 XML Schema 產生 Java 類的技術,它提供了將 XML 實例文檔反向生成 Java 對象樹的方法,并能將 Java 對象樹的內容重新寫到 XML 實例文檔。JAXB 是 JDK 的組成部分。JAXB RI(Reference Implementation)即 schema compiler 能夠將 XML 的 schema 文件映射為相應的 Java 元素。
------ From here: http://www.ibm.com/developerworks/cn/java/j-lo-codemodel/
我覺的:
它就是一個生成Java源代碼的庫 ! (哈! (⌒^⌒)b) !
但是我還是想多說幾句, codemodel和javapoet差不多, 都對java源文件進行了建模, 都有相關的數據結構來表述源文件中固定的概念, 這樣用戶使用起來會更加方便, 只是增加了復雜度和理解上的困難. 其實只要我們按coding的順序(先聲明包...再import依賴包...再聲明class...然后生命成員變量...再然后聲明方法......)來構建也是挺好理解的.
下面貼幾張圖:
對此圖有兩點說明, 右下角顯示:
a. codemodel版權屬于oracle公司
b. 此庫已經很久沒有更新了
點擊左邊導航欄的Download按鈕跳到codemodel的maven倉庫, 這里可以下載codemodel的jar包和源碼. 這里再貼一圖:
此圖說明codemodel從2011年開始就不再更新了 (自從sun被oracle收購之后, oracle對很多java業務就不再關心. 因為某些雞肋的業務不賺錢啊...呵呵...), 有好心的開發者fork了codemodel源碼并進行了維護升級. 如下:
https://github.com/UnquietCode/JCodeModel
https://github.com/phax/jcodemodel
喂喂, 樓主, 這是寫偵探小說么?! 不喜勿噴哈 ヾ _?
- (2) 使用步驟
- 在Android Studio中創建一個
Java Library
Module - 在上一步創建的Module的<module>/build.gradle構建腳本中添加codemodel的依賴庫, 如下:
- 在Android Studio中創建一個
dependencies {
compile 'com.sun.codemodel:codemodel:2.6'
}
* 編寫生成Java源文件的代碼
* (3) 使用案例
package com.example.javawriter;
// 注意不要引用了錯誤的包, 有internal的包是jdk內部使用的包
// import com.sun.codemodel.internal.ClassType;
// import com.sun.codemodel.internal.JDefinedClass
// import com.sun.codemodel.internal.JBlock
// ....
// 下面的包是獨立出來的codemodel庫的包, 這個包是沒有internal的
import com.sun.codemodel.ClassType;
import com.sun.codemodel.JBlock;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JConditional;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JExpr;
import com.sun.codemodel.JFieldVar;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JVar;
import java.io.File;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) throws IOException, JClassAlreadyExistsException {
testCodeModel();
}
/**
* 使用codemodel生成Java源文件:
* 在構建腳本中添加codelmodel的依賴庫 (倉庫為jcenter):
* compile 'com.sun.codemodel:codemodel:2.6'
*
* @throws JClassAlreadyExistsException
* @throws IOException
*/
private static void testCodeModel() throws JClassAlreadyExistsException, IOException {
final String className = "com.stone.generate.Person";
/************ 生成一個Java源文件模型 ************/
JCodeModel model = new JCodeModel();
/************ 為模型添加一個頂級類型 (添加一個類) ************/
JDefinedClass klass = model._class(JMod.PUBLIC, className, ClassType.CLASS);
/************ 添加一個靜態成員變量 ************/
int modifier = JMod.PRIVATE + JMod.STATIC + JMod.FINAL;
JFieldVar jStaticFieldVar = klass.field(modifier, String.class, "TAG");
//jStaticFieldVar.assign(JExpr.lit(klass.fullName())); //error, 不能對未初始化成員變量進行賦值, 要先進行初始化;
jStaticFieldVar.init(JExpr.lit(klass.fullName()));
/************ 添加一個成員變量 ************/
//原始類型變量(int, byte,char ....)才可以使用JType.parse(model, "int"), Object類型直接使用Object.class
//JFieldVar jFieldVar = klass.field(JMod.PRIVATE, JType.parse(model, "String"), "name"); //java.lang.IllegalArgumentException: Not a primitive type: String
JFieldVar jFieldVar = klass.field(JMod.PRIVATE, String.class , "name");
jFieldVar.annotate(MyAnnotation.class); //給字段添加一個注解
/************ 添加一個構造方法 ************/
JMethod constructor = klass.constructor(JMod.PRIVATE);
constructor.param(String.class, "name"); //為構造方法添加一個參數
JBlock constructorBlock = constructor.body();
constructorBlock.assign(JExpr.refthis("name"), constructor.params().get(0)); //初始化成員變量
constructorBlock.directStatement("System.out.println(\"Constructor invoked !\");"); //直接定義語句
/************ 添加一個成員方法 ************/
JMethod jMethod = klass.method(JMod.PUBLIC, Void.TYPE, "setName"); //參數依次為: 修飾符, 返回類型, 方法名
//jMethod.param(JType.parse(model, "String"), "name"); //java.lang.IllegalArgumentException: Not a primitive type: String
jMethod.param(String.class, "name");
JBlock methodBlock = jMethod.body(); //構建方法體
methodBlock.assign(JExpr.refthis("name"), jMethod.params().get(0)); //在方法體中生成一句賦值語句 (為成員變量賦值)
//在方法塊中定義兩個局部變量
//JVar var = jBlock.decl(model.INT, "age"); //先聲明變量, 再進行初始化 (兩步完成)
//var.init(JExpr.lit(23));
JVar var = methodBlock.decl(model.INT, "age", JExpr.lit(100)); //聲明變量, 同時進行初始化 (一步到位)
JVar isAgeGreatThan_25 = methodBlock.decl(model.BOOLEAN, "isAgeGreatThan_25", var.gt(JExpr.lit(25)));
//構造一個if...else...語句塊
JBlock if_else_block = new JBlock();
JConditional jConditional = if_else_block._if(isAgeGreatThan_25);
jConditional._then().directStatement("System.out.println(\"Age great than 25\");"); //語句結束時不要忘了分號
jConditional._else().directStatement("System.out.println(\"Age less than 25\");");
//將if...else...語句塊添加到方法語句塊中
methodBlock.add(if_else_block);
/************ 構建生成一個Java源文件 ************/
model.build(new File("java-demo/src/main/java"));
}
}
上面用到的自定義注解如下:
package com.example.javawriter;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface MyAnnotation {
String value() default "Read the fucking source code !";
}
運行程序, 生成如下Java源文件:

四. 三者的異同
1. 三個庫都是用來生成java源文件的
2. JavaWriter就是一個工具類, 使用起來簡單容易理解, 但是要手動拼接源文件中的語句, 因此容易出出現拼寫錯誤.
3. javapoet和codemodel都對java源文件進行了建模. 結構化java源文件后, 用戶使用時必須使用library提供的數據結構, 代碼的拼接生成由庫處理, 因此不會產生拼寫錯誤, 使用起來也比較方便.
4. 個人感覺javapoet比codemodel使用起來更加方便, 抽象出來的概念也更少, 更加容易理解. codemodel屬于重量級的庫, 它幾乎對java源文件中的所有概念都進行了抽象, 如: 類、字段、方法、變量、語句塊、循環......等等 , 因此使用起來非常繁瑣, 理解起來也更加困難.
5. javapoet和codemodel生成java源文件的步驟是相反的: javapoet是先構建字段、方法、構造器、語句 ...... 等等, 然后添加到一個類(TypeSpec)中, 也就是說, javapoet是先構建細節, 然后再組織整體骨架, 是先分后總的邏輯; codemodel恰恰相反, codemodel構建java源文件非常類似于構建一顆DOM樹, 先構建根節點(JCodeModel), 然后再構建其他分支節點(JDefinedClass、JFieldVar、JMethod ......), 最后再構建比分支節點更細節的支節點 (JBlock、JExpr、JConditional ......)。
五. 使用場景
這三個庫主要就是用來生成Java源文件. 那么什么時候需要生成java源文件呢? 使用注解或解析注解的地方需要(生成那些需要我們重復勞動的代碼 --- 減少我們的負擔!). 那么什么地方會使用注解和解析呢? 使用注解的地方非常多, 如大部分的ORM框架([Realm](https://realm.io/)、[OrmLite](http://ormlite.com/)、[DBFlow](http://www.appance.com/dbflow/)、[ActiveAndroid](http://www.activeandroid.com/)、[greenDAO](http://greenrobot.org/greendao/)... 等)、依賴注入框架([Dagger](http://square.github.io/dagger/)、[ButterKnife](http://jakewharton.github.io/butterknife/)、 [guice](https://github.com/google/guice)、[RoboGuice](https://github.com/roboguice/roboguice) ... 等)、編譯時檢查框架(support-annotations、[jcip](https://github.com/jcip/jcip.github.com) ...等)以及很多其他優秀框架([Retrofit](http://square.github.io/retrofit/)、[retrolambda](https://github.com/orfjackal/retrolambda), [PermissionsDispatcher](http://hotchemi.github.io/PermissionsDispatcher/), [RxPermissions](https://github.com/tbruyelle/RxPermissions), [EventBus](http://greenrobot.org/eventbus/) ... 等) ......
references
[用 Java 生成 Java - CodeModel 介紹](http://www.ibm.com/developerworks/cn/java/j-lo-codemodel/)
[codemodel](https://codemodel.java.net/)
[javapoet](https://github.com/square/javapoet)