自定義註解生成代碼(三) 之javapoet案例實戰
前面兩個章節主要介紹了 javapoet 的基礎語法和基本使用,感興趣的同學可以查看博客 自定義註解生成代碼(一)之 javaPoetAPI詳解 和 自定義註解生成代碼(二) 之javapoetAPI使用舉例
本章我打算使用更加貼近實戰的案例鞏固大家對javapoet的認識,廢話不多說,直接上代碼。
總結:
JavaPoet是square推出的開源java代碼生成框架,提供Java Api生成.java源文件。這個框架功能非常有用,我們可以很方便的使用它根據註解、數據庫模式、協議格式等來對應生成代碼。通過這種自動化生成代碼的方式,可以讓我們用更加簡潔優雅的方式要替代繁瑣冗雜的重複工作。
官方源碼 :https://github.com/square/javapoet
2.2 關鍵類說明
類名 | 說明 | 備註 |
---|---|---|
JavaFile | A Java file containing a single top level class | 用於構造輸出包含一個頂級類的Java文件 |
TypeSpec | A generated class, interface, or enum declaration | 生成類,接口,或者枚舉 |
MethodSpec | A generated constructor or method declaration | 生成構造函數或方法 |
FieldSpec | A generated field declaration | 生成成員變量或字段 |
ParameterSpec | A generated parameter declaration | 用來創建參數 |
AnnotationSpec | A generated annotation on a declaration | 用來創建註解 |
在JavaPoet中,JavaFile是對.java文件的抽象,TypeSpec是類/接口/枚舉的抽象,MethodSpec是方法/構造函數的抽象,FieldSpec是成員變量/字段的抽象。這幾個類各司其職,但都有共同的特點,提供內部Builder供外部更多更好地進行一些參數的設置以便有層次的擴展性的構造對應的內容。
另外,它提供$L(for Literals), $S(for Strings), $T(for Types), $N(for Names)等標識符,用於佔位替換。
官網介紹:https://docs.gradle.org/4.10.1/userguide/java_plugin.html#sec:incremental_annotation_processing
第一步 , 從 maven 倉庫,找到相對穩定的版本引用 https://mvnrepository.com/
第二部 ,在項目對用moudle的 build.gradle文件中添加對應引用
implementation 'com.squareup:javapoet:1.11.1'
第三步 , 愉快的開始使用
案例一 使用javapoet生成簡單的helloword
-
生成後的代碼如下:
package com.example.helloworld; public final class HelloWorld { public static void main(String[] args) { System.out.println("Hello, JavaPoet!"); } }
-
javapoet代碼,
生成代碼生成一般是由內向外的,先生成裏面的主方法然後生成外層的主類 HelloWorld ,最後將主類寫入到 HelloWord.java 文件 或 者直接打印到控制檯
public static void main(String[] args) {
//使用 MethodSpec 主方法main生成
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 helloWorld = TypeSpec.classBuilder("HelloWorld") //主類的名稱
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.addJavadoc("註釋註解代碼塊")
.build();
try {
JavaFile javaFile = JavaFile.builder("com.zbc.latte_compiler.javapoeatdemo", helloWorld)
.build();
/**
* 代碼寫入控制檯
*/
javaFile.writeTo(System.out);
/**
* 代碼寫入文件 E:\\FastEc\\latte_compiler\\src\\main\\java
*/
File file = new File("latte_compiler\\src\\main\\java");
System.out.println("___" + file.getAbsolutePath());
javaFile.writeTo(file);
} catch (Exception e) {
e.printStackTrace();
}
}
運行上面的代碼 ,可以在控制檯輸出生產的java代碼
- 在控制檯可以得到打印結果
JavaFile對象爲我們提拱了 javaFile.writeTo()方法 ,可以將生成好的代碼內容(符合java格式的字符串)寫入到 Path 、文件路徑、文件、System.out(控制檯)等
其中 javaFile.writeTo(System.out); 可以將我們生成後的代碼寫入到控制檯
當然如果我們的代碼通常是要寫到
.java
文件的,我們只需要使用如下代碼
javaFile.writeTo(new File("latte_compiler\\src\\main\\java"));
其中文件路徑應定位到你想要生成報名的對應moudle下,當然這裏還是建議使用相對路徑的,於是最終代碼文件實際生成的位置是 new File(“latte_compiler\src\main\java”).getPath()+"\包名\helloWorld.java",有意思的是javapoet幫助我們完成了package的創建,使用的使我們提供的包名
實戰案例二 ,生成模擬網絡請求的實戰案例代碼
先生成網絡回調接口,生成後的代碼如下
package com.zbc.latte_compiler.javapoeatdemo;
import java.lang.Object;
import java.lang.String;
/**
* 網絡請求接口
*/
interface BaseCallback {
String SUCCESS = "msgSuccess";
String FAIL = "msgFail";
void onStart();
void onFinish();
void onError(int errorCode, String msg);
void onSuccess(Object o);
}
javaPoet代碼如下
/**
* 主接口代碼生成
*/
private static void createInfe() {
//定義靜態成員變量
FieldSpec msgSuccess = FieldSpec.builder(String.class, "SUCCESS")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "msgSuccess")
.build();
//定義靜態成員變量
FieldSpec msgFail = FieldSpec.builder(String.class, "FAIL")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$S", "msgFail")
.build();
//定義 onStart 抽象方法
MethodSpec onStart = MethodSpec.methodBuilder("onStart")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.build();
//定義 onFinish 抽象方法
MethodSpec onFinish = MethodSpec.methodBuilder("onFinish")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.build();
//定義 onError 抽象方法
MethodSpec onError = MethodSpec.methodBuilder("onError")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addParameter(int.class, "errorCode")
.addParameter(String.class, "msg")
.build();
//定義 onSuccess 抽象方法
MethodSpec onSuccess = MethodSpec.methodBuilder("onSuccess")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addParameter(Object.class, "o")
.build();
//定義 BaseCallback 接口,併爲接口添加方法
TypeSpec baseAc = TypeSpec.interfaceBuilder("BaseCallback")
.addMethod(onStart)
.addMethod(onFinish)
.addMethod(onError)
.addMethod(onSuccess)
.addField(msgSuccess)
.addField(msgFail)
.addJavadoc("網絡請求接口")
.build();
//完成代碼輸出
try {
JavaFile javaFile = JavaFile.builder("com.zbc.latte_compiler.javapoeatdemo", baseAc)
.build();
/**
* 代碼寫入控制檯
*/
javaFile.writeTo(System.out);
/**
* 代碼寫入文件 \com\zbc\latte_compiler
*/
File file = new File("latte_compiler\\src\\main\\java");
System.out.println("___" + new File("latte_compiler\\src\\main\\java").getAbsolutePath());
javaFile.writeTo(file);
} catch (Exception e) {
e.printStackTrace();
}
}
java文件完美生成
生成回調實體類,生成後代碼如下
package com.zbc.latte_compiler.javapoeatdemo;
import java.lang.String;
/**
* 請求實體Bean */
class BaseEntity {
private int code;
private String msg;
private DataBean data;
private int getCode() {
return this.code;
}
private void setCode(int code) {
this.code = code;
}
private DataBean getData() {
return this.data;
}
private void setData(DataBean data) {
this.data = data;
}
/**
* 內部類生成 */
static class DataBean {
private String name;
private String getName() {
return this.name;
}
private void setName(String name) {
this.name = name;
}
}
}
javaPoet代碼如下
private static void grenerateBaseEntity(String className) {
FieldSpec code = FieldSpec.builder(int.class, "code")
.addModifiers(Modifier.PRIVATE)
.build();
FieldSpec msg = FieldSpec.builder(String.class, "msg")
.addModifiers(Modifier.PRIVATE)
.build();
FieldSpec data = FieldSpec.builder(BaseEntity.DataBean.class, "data")
.addModifiers(Modifier.PRIVATE)
.build();
MethodSpec getCode = MethodSpec.methodBuilder("getCode")
.addModifiers(Modifier.PRIVATE)
//定義返回值類型
.returns(int.class)
//代碼方法內添加通用代碼
.addStatement("return this.code")
.build();
MethodSpec setCode = MethodSpec.methodBuilder("setCode")
.addModifiers(Modifier.PRIVATE)
//參數接收
.addParameter(int.class, "code")
.addStatement("this.code = code")
.build();
MethodSpec getData = MethodSpec.methodBuilder("getData")
.addModifiers(Modifier.PRIVATE)
//定義返回值類型
.returns(BaseEntity.DataBean.class)
//代碼方法內添加通用代碼
.addStatement("return this.data")
.build();
MethodSpec setData = MethodSpec.methodBuilder("setData")
.addModifiers(Modifier.PRIVATE)
//參數接收
.addParameter(BaseEntity.DataBean.class, "data")
.addStatement("this.data = data")
.build();
FieldSpec name = FieldSpec.builder(String.class, "name")
.addModifiers(Modifier.PRIVATE)
.build();
MethodSpec getName = MethodSpec.methodBuilder("getName")
.addModifiers(Modifier.PRIVATE)
//定義返回值類型
.returns(String.class)
//代碼方法內添加通用代碼
.addStatement("return this.name")
.build();
MethodSpec setName = MethodSpec.methodBuilder("setName")
.addModifiers(Modifier.PRIVATE)
//參數接收,這裏注意,如果是int 就直接寫int不用裝箱
.addParameter(String.class, "name")
.addStatement("this.name = name")
.build();
//內部類生成
TypeSpec dataBean = TypeSpec.classBuilder("DataBean")
.addField(name)
.addModifiers(Modifier.STATIC)
.addMethod(getName)
.addMethod(setName)
.addJavadoc("內部類生成")
.build();
TypeSpec baseAc = TypeSpec.classBuilder(className)
.addField(code)
.addField(msg)
.addField(data)
.addMethod(getCode)
.addMethod(setCode)
.addMethod(getData)
.addMethod(setData)
.addType(dataBean)
.addJavadoc("請求實體Bean")
.build();
try {
JavaFile javaFile = JavaFile.builder("com.zbc.latte_compiler.javapoeatdemo", baseAc)
.build();
/**
* 代碼寫入控制檯
*/
javaFile.writeTo(System.out);
/**
* 代碼寫入文件 \com\zbc\latte_compiler
*/
File file = new File("latte_compiler\\src\\main\\java");
System.out.println("___" + new File("latte_compiler\\src\\main\\java").getAbsolutePath());
javaFile.writeTo(file);
} catch (Exception e) {
e.printStackTrace();
}
}
創建網絡請求類,最終生成的代碼如下
package com.zbc.latte_compiler.javapoeatdemo;
public class DataClient {
private String tag = "default_tag";
private String url;
private long time = System.currentTimeMillis();
public DataClient build() {
time = System.currentTimeMillis();
return this;
}
public DataClient setTag(String tag) {
this.tag = tag;
return this;
}
public DataClient get(String url) {
this.url = url;
return this;
}
public void execute(BaseCallback baseCallback) {
if (baseCallback != null) {
baseCallback.onStart();
}
if (baseCallback != null) {
BaseEntity baseEntity = new BaseEntity();
baseEntity.setCode(200);
BaseEntity.DataBean dataBean = new BaseEntity.DataBean();
dataBean.setName("請求到的數據_____data");
baseEntity.setData(dataBean);
baseCallback.onSuccess(baseEntity);
}
if (baseCallback != null) {
baseCallback.onFinish();
}
}
}
javapoet代碼如下
private static void greanerateDataClient() {
FieldSpec tag = FieldSpec.builder(String.class, "tag")
.addModifiers(Modifier.PRIVATE)
//$S for Strings
//當輸出的代碼包含字符串的時候, 可以使用 $S 表示一個 string。
.initializer("$S", "default_tag")
.build();
FieldSpec url = FieldSpec.builder(String.class, "url")
.addModifiers(Modifier.PRIVATE)
.build();
FieldSpec time = FieldSpec.builder(long.class, "time")
.addModifiers(Modifier.PRIVATE)
//$T for Types
//使用Java內置的類型會使代碼比較容易理解。JavaPoet極大的支持這些類型,通過 $T 進行映射,會自動import聲明。
.initializer("$T.currentTimeMillis()", System.class)
.build();
//得到一個不存在的類
ClassName DataClient = ClassName.get("com.zbc.latte_compiler.javapoeatdemo", "DataClient");
MethodSpec build = MethodSpec.methodBuilder("build")
.addJavadoc("build方法")
.addModifiers(Modifier.PUBLIC)
.returns(DataClient)
.addStatement("time = $T.currentTimeMillis()", System.class)
//添加單行代碼
.addStatement("return this")
.build();
MethodSpec setTag = MethodSpec.methodBuilder("setTag")
.addJavadoc("setTag方法")
.addModifiers(Modifier.PUBLIC)
.returns(DataClient)
.addParameter(String.class, "tag")
.addStatement("this.tag=tag")
//添加單行代碼
.addStatement("return this")
.build();
MethodSpec urlM = MethodSpec.methodBuilder("get")
.addJavadoc("get方法")
.addModifiers(Modifier.PUBLIC)
//設置返回值類型
.returns(DataClient)
.addParameter(String.class, "url")
.addStatement("this.url=url")
//添加單行代碼
.addStatement("return this")
.build();
MethodSpec execute = MethodSpec.methodBuilder("execute")
.addJavadoc("execute 方法")
.addModifiers(Modifier.PUBLIC)
.addParameter(BaseCallback.class, "baseCallback")
//添加代碼塊
.addCode(" if (baseCallback != null) {\n" +
" baseCallback.onStart();\n" +
" }\n")
.addCode("if (baseCallback != null) { \n")
.addStatement(" $T baseEntity = new $T()", BaseEntity.class, BaseEntity.class)
.addCode(" baseEntity.setCode(200);\n" +
" BaseEntity.DataBean dataBean = new BaseEntity.DataBean();\n" +
" dataBean.setName(\"請求到的數據_____data\");\n" +
" baseEntity.setData(dataBean);\n" +
" baseCallback.onSuccess(baseEntity);\n" +
" }\n" +
"if (baseCallback != null) {\n" +
" baseCallback.onFinish();\n" +
"}\n")
.build();
TypeSpec dataClient = TypeSpec.classBuilder("DataClient")
.addField(tag)
.addField(url)
.addField(time)
.addMethod(build)
.addMethod(setTag)
.addMethod(urlM)
.addMethod(execute)
.addJavadoc("網絡請求器")
.build();
try {
JavaFile javaFile = JavaFile.builder("com.zbc.latte_compiler.javapoeatdemo", dataClient)
.build();
/**
* 代碼寫入控制檯
*/
javaFile.writeTo(System.out);
/**
* 代碼寫入文件 \com\zbc\latte_compiler
*/
File file = new File("latte_compiler\\src\\main\\java");
System.out.println("___" + new File("latte_compiler\\src\\main\\java").getAbsolutePath());
javaFile.writeTo(file);
} catch (Exception e) {
e.printStackTrace();
}
}
生成調用代碼,最終生成後的代碼如下
package com.zbc.latte_compiler.javapoeatdemo;
public class DataT {
public void loadData() {
String url = "http://www.baidu.com";
new DataClient()
.build()
.setTag(this.getClass().getCanonicalName())
.get(url)
.execute(new BaseCallback() {
@Override
public void onStart() {
}
@Override
public void onFinish() {
}
@Override
public void onError(int errorCode, String msg) {
}
@Override
public void onSuccess(Object o) {
}
});
}
}
生成上述代碼的javapoet代碼如下
private static void greanerateCallMethod() {
//得到一個不存在的類
ClassName DataClient = ClassName.get("com.zbc.latte_compiler.javapoeatdemo", "DataClient");
ClassName BaseCallback = ClassName.get("com.zbc.latte_compiler.javapoeatdemo", "BaseCallback");
MethodSpec loadData = MethodSpec.methodBuilder("loadData")
.addJavadoc("loadData 方法")
.addModifiers(Modifier.PUBLIC)
//添加代碼塊
.addCode(" String url = $S;\n", "http://www.baidu.com")
.addCode("new $T()\n" +
" .build()\n" +
" .setTag(this.getClass().getCanonicalName())\n" +
" .get(url)\n" +
" .execute(new $T(){\n" +
" @Override\n" +
" public void onStart() {\n" +
"\n" +
" }\n" +
" @Override\n" +
" public void onFinish() {\n" +
"\n" +
" }\n" +
"\n" +
" @Override\n" +
" public void onError(int errorCode, String msg) {\n" +
"\n" +
" }\n" +
"\n" +
" @Override\n" +
" public void onSuccess(Object o) {\n" +
"\n" +
" }\n" +
" }); \n",
DataClient, BaseCallback
)
.build();
TypeSpec dataClient = TypeSpec.classBuilder("CallMethod")
// .addField(tag)
.addMethod(loadData)
.addJavadoc("網絡請求數據的調用")
.build();
try {
JavaFile javaFile = JavaFile.builder("com.zbc.latte_compiler.javapoeatdemo", dataClient)
.build();
/**
* 代碼寫入控制檯
*/
javaFile.writeTo(System.out);
/**
* 代碼寫入文件 \com\zbc\latte_compiler
*/
File file = new File("latte_compiler\\src\\main\\java");
System.out.println("___" + new File("latte_compiler\\src\\main\\java").getAbsolutePath());
javaFile.writeTo(file);
} catch (Exception e) {
e.printStackTrace();
}
}