開(kāi)發(fā)工具為Android Studio
一. 使用JavaWriter生成java源文件
- (1) 介紹
JavaWriter是square開(kāi)源項(xiàng)目javapoet中的一個(gè)分支, JavaWriter的整個(gè)庫(kù)中有一個(gè)關(guān)鍵的類com.squareup.javawriter.JavaWriter
(一共只有兩個(gè)類), 主要用來(lái)生成Java源文件, 使用鏈?zhǔn)秸{(diào)用依次構(gòu)建Java源文件. JavaWriter生成Java源文件的方式比較原始。由于整個(gè)庫(kù)一共才兩個(gè)類, 因此沒(méi)有對(duì)Java源代碼進(jìn)行建模, 如class勺拣、field、method等沒(méi)有對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu), 只有對(duì)應(yīng)的方法來(lái)構(gòu)建生成這些固定的概念蓝丙。JavaWriter使用起來(lái)比較簡(jiǎn)單,整個(gè)庫(kù)也非常小。官網(wǎng)介紹如下:
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中創(chuàng)建一個(gè)
Java library
Module - 在上一步創(chuàng)建的module的構(gòu)建腳本
<module>/build.gradle
中添加JavaWriter的依賴, 如下:
- 在Android Studio中創(chuàng)建一個(gè)
dependencies {
compile 'com.squareup:javawriter:2.5.1'
}
編寫(xiě)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
* 使用下面語(yǔ)句引用該庫(kù) (倉(cāng)庫(kù)為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") //注意不要使用分號(hào)結(jié)束return語(yǔ)句
.endMethod()
.endType()
.close();
}
}
運(yùn)行程序, 生成源文件如下:
詳細(xì)用法可以參考官網(wǎng)用例https://github.com/square/javapoet/blob/javawriter_2/src/test/java/com/squareup/javawriter/JavaWriterTest.java
二. 使用javapoet生成Java源文件
- (1) 介紹
javapoet是大名鼎鼎的square公司開(kāi)源的一個(gè)項(xiàng)目, github地址: https://github.com/square/javapoet. javapoet要比JavaWriter稍微復(fù)雜. 此庫(kù)定義了一系列的數(shù)據(jù)結(jié)構(gòu)來(lái)表示Java源文件中某些固定的概念, 如class、interface吁恍、annoation、field播演、method等冀瓦。javapoet對(duì)java源文件的結(jié)構(gòu)進(jìn)行了建模, 其模型如下:
javapoet官網(wǎng)對(duì)其介紹如下:
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中創(chuàng)建一個(gè)
Java Library
Module - 在上一步創(chuàng)建的Module的<module>/build.gradle構(gòu)建腳本中添加javapoet庫(kù)的依賴, 如下:
- 在Android Studio中創(chuàng)建一個(gè)
dependencies {
compile 'com.squareup:javapoet:1.7.0'
}
編寫(xiě)生成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();
}
/**
* 庫(kù): https://github.com/square/javapoet/
* 使用下面語(yǔ)句引用javapoet (倉(cāng)庫(kù)為jcenter):
* compile 'com.squareup:javawriter:2.5.1'
*
* 使用javapoet生成java源文件的步驟 (1,2,3步驟可以交換):
* 1. 構(gòu)建成員變量
* 2. 構(gòu)建構(gòu)造方法
* 3. 構(gòu)建方法(static/concrete)
* 4. 構(gòu)建類型(enum/annotation/interface/class)
* 5. 構(gòu)建java源文件
* 6. 輸出java源文件到文件系統(tǒng)
*/
private static void testJavaPoet() {
String packageName = "com.stone.demo.javawriter";
String className = "HelloWorld";
//1. 生成一個(gè)字段
FieldSpec fieldSpec = FieldSpec.builder(String.class, "var", Modifier.PUBLIC).build();
//2. 生成一個(gè)方法 (方式一: 面向代碼, 更為底層的構(gòu)建方式)
MethodSpec mainMethod = MethodSpec.methodBuilder("main") //設(shè)置方法名稱
.addModifiers(Modifier.PUBLIC, Modifier.STATIC) //添加修飾符
.addParameter(String[].class, "args") //添加參數(shù)
.returns(TypeName.VOID) //添加返回值
.addStatement("$T.out.println($S)", System.class, "Hello world !") //添加代碼語(yǔ)句 (結(jié)束語(yǔ)句的分號(hào)不需要, 注意與CodeBlock的區(qū)別)
.build();
//2. 生成一個(gè)方法 (方式二: 對(duì)方法建模, 結(jié)構(gòu)化的構(gòu)建)
// ParameterSpec parameterSpec = ParameterSpec.builder(String[].class, "args").build(); //構(gòu)建參數(shù)模型
// CodeBlock codeBlock = CodeBlock.of("$T.out.println($S);", System.class, "Hello world"); //構(gòu)建代碼塊 (語(yǔ)句結(jié)束的分號(hào)不能少)
// MethodSpec methodSpec = MethodSpec.methodBuilder("main") //設(shè)置方法名稱
// .addModifiers(Modifier.PUBLIC, Modifier.STATIC) //添加修飾符
// .returns(TypeName.VOID) //添加返回值
// .addParameter(parameterSpec) //添加方法參數(shù)
// .addCode(codeBlock) //添加代碼塊
// .build();
//3. 生成類型(enum/class/annotation/interface)
TypeSpec hellworld = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC)
.addField(fieldSpec)
.addMethod(mainMethod)
// .addMethod(methodSpec)
.build();
//4. 構(gòu)建Java源文件
JavaFile javaFile = JavaFile.builder(packageName, hellworld).build();
//5. 輸出java源文件到文件系統(tǒng)
try {
//輸出到控制臺(tái)
// javaFile.writeTo(System.out);
//生成java源文件到AndroidStudio的當(dāng)前Module中
generateToCurrentAndroidStudioModule(javaFile);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 生成到當(dāng)前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(), 參數(shù)為源碼生成目錄(源碼的classpath目錄)
}
}
運(yùn)行程序, 生成如下源文件:
詳細(xì)用法, 參考官網(wǎng)用例:
https://github.com/square/javapoet/tree/master/src/test/java/com/squareup/javapoet
三. 使用codemodel生成Java源文件
- (1) 介紹
官網(wǎng)如是說(shuō):
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 庫(kù),它提供了一種通過(guò) Java 程序來(lái)生成 Java 程序的方法写烤。
CodeModel 項(xiàng)目是 JAXB 的子項(xiàng)目翼闽。JAXB(Java Architecture for XML Binding)是一項(xiàng)可以根據(jù) XML Schema 產(chǎn)生 Java 類的技術(shù),它提供了將 XML 實(shí)例文檔反向生成 Java 對(duì)象樹(shù)的方法洲炊,并能將 Java 對(duì)象樹(shù)的內(nèi)容重新寫(xiě)到 XML 實(shí)例文檔感局。JAXB 是 JDK 的組成部分。JAXB RI(Reference Implementation)即 schema compiler 能夠?qū)?XML 的 schema 文件映射為相應(yīng)的 Java 元素暂衡。
------ From here: http://www.ibm.com/developerworks/cn/java/j-lo-codemodel/
我覺(jué)的:
它就是一個(gè)生成Java源代碼的庫(kù) ! (哈! (⌒^⌒)b) !
但是我還是想多說(shuō)幾句, codemodel和javapoet差不多, 都對(duì)java源文件進(jìn)行了建模, 都有相關(guān)的數(shù)據(jù)結(jié)構(gòu)來(lái)表述源文件中固定的概念, 這樣用戶使用起來(lái)會(huì)更加方便, 只是增加了復(fù)雜度和理解上的困難. 其實(shí)只要我們按coding的順序(先聲明包...再import依賴包...再聲明class...然后生命成員變量...再然后聲明方法......)來(lái)構(gòu)建也是挺好理解的.
下面貼幾張圖:
對(duì)此圖有兩點(diǎn)說(shuō)明, 右下角顯示:
a. codemodel版權(quán)屬于oracle公司
b. 此庫(kù)已經(jīng)很久沒(méi)有更新了
點(diǎn)擊左邊導(dǎo)航欄的Download按鈕跳到codemodel的maven倉(cāng)庫(kù), 這里可以下載codemodel的jar包和源碼. 這里再貼一圖:
此圖說(shuō)明codemodel從2011年開(kāi)始就不再更新了 (自從sun被oracle收購(gòu)之后, oracle對(duì)很多java業(yè)務(wù)就不再關(guān)心. 因?yàn)槟承╇u肋的業(yè)務(wù)不賺錢啊...呵呵...), 有好心的開(kāi)發(fā)者fork了codemodel源碼并進(jìn)行了維護(hù)升級(jí). 如下:
https://github.com/UnquietCode/JCodeModel
https://github.com/phax/jcodemodel
喂喂, 樓主, 這是寫(xiě)偵探小說(shuō)么?! 不喜勿噴哈 ヾ _?
- (2) 使用步驟
- 在Android Studio中創(chuàng)建一個(gè)
Java Library
Module - 在上一步創(chuàng)建的Module的<module>/build.gradle構(gòu)建腳本中添加codemodel的依賴庫(kù), 如下:
- 在Android Studio中創(chuàng)建一個(gè)
dependencies {
compile 'com.sun.codemodel:codemodel:2.6'
}
* 編寫(xiě)生成Java源文件的代碼
* (3) 使用案例
package com.example.javawriter;
// 注意不要引用了錯(cuò)誤的包, 有internal的包是jdk內(nèi)部使用的包
// import com.sun.codemodel.internal.ClassType;
// import com.sun.codemodel.internal.JDefinedClass
// import com.sun.codemodel.internal.JBlock
// ....
// 下面的包是獨(dú)立出來(lái)的codemodel庫(kù)的包, 這個(gè)包是沒(méi)有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源文件:
* 在構(gòu)建腳本中添加codelmodel的依賴庫(kù) (倉(cāng)庫(kù)為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";
/************ 生成一個(gè)Java源文件模型 ************/
JCodeModel model = new JCodeModel();
/************ 為模型添加一個(gè)頂級(jí)類型 (添加一個(gè)類) ************/
JDefinedClass klass = model._class(JMod.PUBLIC, className, ClassType.CLASS);
/************ 添加一個(gè)靜態(tài)成員變量 ************/
int modifier = JMod.PRIVATE + JMod.STATIC + JMod.FINAL;
JFieldVar jStaticFieldVar = klass.field(modifier, String.class, "TAG");
//jStaticFieldVar.assign(JExpr.lit(klass.fullName())); //error, 不能對(duì)未初始化成員變量進(jìn)行賦值, 要先進(jìn)行初始化;
jStaticFieldVar.init(JExpr.lit(klass.fullName()));
/************ 添加一個(gè)成員變量 ************/
//原始類型變量(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); //給字段添加一個(gè)注解
/************ 添加一個(gè)構(gòu)造方法 ************/
JMethod constructor = klass.constructor(JMod.PRIVATE);
constructor.param(String.class, "name"); //為構(gòu)造方法添加一個(gè)參數(shù)
JBlock constructorBlock = constructor.body();
constructorBlock.assign(JExpr.refthis("name"), constructor.params().get(0)); //初始化成員變量
constructorBlock.directStatement("System.out.println(\"Constructor invoked !\");"); //直接定義語(yǔ)句
/************ 添加一個(gè)成員方法 ************/
JMethod jMethod = klass.method(JMod.PUBLIC, Void.TYPE, "setName"); //參數(shù)依次為: 修飾符, 返回類型, 方法名
//jMethod.param(JType.parse(model, "String"), "name"); //java.lang.IllegalArgumentException: Not a primitive type: String
jMethod.param(String.class, "name");
JBlock methodBlock = jMethod.body(); //構(gòu)建方法體
methodBlock.assign(JExpr.refthis("name"), jMethod.params().get(0)); //在方法體中生成一句賦值語(yǔ)句 (為成員變量賦值)
//在方法塊中定義兩個(gè)局部變量
//JVar var = jBlock.decl(model.INT, "age"); //先聲明變量, 再進(jìn)行初始化 (兩步完成)
//var.init(JExpr.lit(23));
JVar var = methodBlock.decl(model.INT, "age", JExpr.lit(100)); //聲明變量, 同時(shí)進(jìn)行初始化 (一步到位)
JVar isAgeGreatThan_25 = methodBlock.decl(model.BOOLEAN, "isAgeGreatThan_25", var.gt(JExpr.lit(25)));
//構(gòu)造一個(gè)if...else...語(yǔ)句塊
JBlock if_else_block = new JBlock();
JConditional jConditional = if_else_block._if(isAgeGreatThan_25);
jConditional._then().directStatement("System.out.println(\"Age great than 25\");"); //語(yǔ)句結(jié)束時(shí)不要忘了分號(hào)
jConditional._else().directStatement("System.out.println(\"Age less than 25\");");
//將if...else...語(yǔ)句塊添加到方法語(yǔ)句塊中
methodBlock.add(if_else_block);
/************ 構(gòu)建生成一個(gè)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 !";
}
運(yùn)行程序, 生成如下Java源文件:
![codemodel生成的Java源文件.png](http://upload-images.jianshu.io/upload_images/1642441-497df81a186a5f3d.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
四. 三者的異同
1. 三個(gè)庫(kù)都是用來(lái)生成java源文件的
2. JavaWriter就是一個(gè)工具類, 使用起來(lái)簡(jiǎn)單容易理解, 但是要手動(dòng)拼接源文件中的語(yǔ)句, 因此容易出出現(xiàn)拼寫(xiě)錯(cuò)誤.
3. javapoet和codemodel都對(duì)java源文件進(jìn)行了建模. 結(jié)構(gòu)化java源文件后, 用戶使用時(shí)必須使用library提供的數(shù)據(jù)結(jié)構(gòu), 代碼的拼接生成由庫(kù)處理, 因此不會(huì)產(chǎn)生拼寫(xiě)錯(cuò)誤, 使用起來(lái)也比較方便.
4. 個(gè)人感覺(jué)javapoet比codemodel使用起來(lái)更加方便, 抽象出來(lái)的概念也更少, 更加容易理解. codemodel屬于重量級(jí)的庫(kù), 它幾乎對(duì)java源文件中的所有概念都進(jìn)行了抽象, 如: 類蓝厌、字段、方法古徒、變量、語(yǔ)句塊读恃、循環(huán)......等等 , 因此使用起來(lái)非常繁瑣, 理解起來(lái)也更加困難.
5. javapoet和codemodel生成java源文件的步驟是相反的: javapoet是先構(gòu)建字段隧膘、方法、構(gòu)造器寺惫、語(yǔ)句 ...... 等等, 然后添加到一個(gè)類(TypeSpec)中, 也就是說(shuō), javapoet是先構(gòu)建細(xì)節(jié), 然后再組織整體骨架, 是先分后總的邏輯疹吃; codemodel恰恰相反, codemodel構(gòu)建java源文件非常類似于構(gòu)建一顆DOM樹(shù), 先構(gòu)建根節(jié)點(diǎn)(JCodeModel), 然后再構(gòu)建其他分支節(jié)點(diǎn)(JDefinedClass、JFieldVar西雀、JMethod ......), 最后再構(gòu)建比分支節(jié)點(diǎn)更細(xì)節(jié)的支節(jié)點(diǎn) (JBlock萨驶、JExpr、JConditional ......)艇肴。
五. 使用場(chǎng)景
這三個(gè)庫(kù)主要就是用來(lái)生成Java源文件. 那么什么時(shí)候需要生成java源文件呢? 使用注解或解析注解的地方需要(生成那些需要我們重復(fù)勞動(dòng)的代碼 --- 減少我們的負(fù)擔(dān)!). 那么什么地方會(huì)使用注解和解析呢? 使用注解的地方非常多, 如大部分的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) ... 等)、編譯時(shí)檢查框架(support-annotations丑孩、[jcip](https://github.com/jcip/jcip.github.com) ...等)以及很多其他優(yōu)秀框架([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)