Javapoet原始碼解析

wangyy發表於2018-04-24

Javapoet:是生成.java原始檔的開源API

 以生成一個HelloWrold.java檔案為例:

1 package com.example.helloworld;
2 
3 public final class HelloWorld {
4   public static void main(String[] args) {
5     System.out.println("Hello, JavaPoet!");
6   }
7 }
 1 MethodSpec main = MethodSpec.methodBuilder("main")
 2     .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
 3     .returns(void.class)
 4     .addParameter(String[].class, "args")
 5     .addStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
 6     .build();
 7 
 8 TypeSpec helloWorld = TypeSpec.classBuilder("HelloWorld")
 9     .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
10     .addMethod(main)
11     .build();
12 
13 JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
14     .build();
15 
16 ///javaFile.writeTo(System.out);  //System.out print  to the console
17  //generate a java file in user.dir
18 String directory = System.getProperty("user.dir");
File file = new File(directory + SEPARATOR + "HelloWrold.java"); 22 PrintStream ps = new PrintStream(new FileOutputStream(file)); 23 javaFile.writeTo(ps); 24 ps.flush(); 25 ps.close();

在這個例子中用到了三個類的物件 :JavaFile  MethodSpec   TypeSpec,首先來看第一個JavaFile

1  public static Builder builder(String packageName, TypeSpec typeSpec) {
2     checkNotNull(packageName, "packageName == null");
3     checkNotNull(typeSpec, "typeSpec == null");
4     return new Builder(packageName, typeSpec);
5   }

Builder是JavaFile中的一個靜態內部類,由此可以看到 Javapoet使用的是建造者設計模式。通過packageName 和TypeSpec生成一個Builder物件,然後呼叫Builder類的build方法:

1 public JavaFile build() {
2       return new JavaFile(this);
3     }

返回當前的JavaFile物件,然後呼叫writeto方法將該物件寫入流/檔案。

接下來看看構造Builder方法時使用的TypeSpec是什麼。

staticBlock:A generated class, interface, or enum declaration:對於要生成的類,介面或列舉的宣告。就是定義 類 介面或者列舉,定義的時候需要給他們命名,新增許可權修飾符 是否靜態 是否抽象 是否final,新增方法等操作

所以這個類裡面有靜態方法classBuilder interfaceBuilder enumBuilder 以及匿名內部類anonymousClassBuilder,這些靜態方法都返回對應的一個Builder物件

該Builder物件是TypeSpec中的靜態內部類,用於構建當前的TypeSpec物件。在內部類Builder中應該有新增許可權修飾符/註釋/註解/變數等操作來構建一個類或者介面列舉

MethodSpec:A generated constructor or method declaration. 對方法的宣告,包括構造方法。所以這個類裡面有methodBuilder constructorBuilder,返回該類的一個Builder物件,這個builder也是該類的一個靜態內部類。並且這個Builder內部類中同樣包括新增許可權修飾符/註釋/註解等方法,另外方法有返回值,引數 Comment 語句來構建一個方法

同樣的原理,FieldSpec是對成員變數的宣告,而且裡面也有一個可以返回當前物件的builder靜態內部類,在這個builder中同樣有新增註釋註解訪問修飾符等的操作,來構建一個變數。

類變數等已經定義完畢,接下來我們分析一下是如何生成java檔案的,回到JavaFile的writeTo方法

這裡有很多過載的writeTo方法,最終都會呼叫

 1  public void writeTo(Appendable out) throws IOException {
 2     // First pass: emit the entire class, just to collect the types we'll need to import.
 3     CodeWriter importsCollector = new CodeWriter(NULL_APPENDABLE, indent, staticImports);
 4     emit(importsCollector);
 5     Map<String, ClassName> suggestedImports = importsCollector.suggestedImports();
 6 
 7     // Second pass: write the code, taking advantage of the imports.
 8     CodeWriter codeWriter = new CodeWriter(out, indent, suggestedImports, staticImports);
 9     emit(codeWriter);
10   }
CodeWriter是什麼呢?CodeWriter將JavaFile轉換為適用於人類和javac使用的字串,實現了匯入,縮排和延期變數名稱。

以FieldSpec為例來說明CodeWriter的用法:

 1 void emit(CodeWriter codeWriter, Set<Modifier> implicitModifiers) throws IOException {
 2     codeWriter.emitJavadoc(javadoc);
 3     codeWriter.emitAnnotations(annotations, false);
 4     codeWriter.emitModifiers(modifiers, implicitModifiers);
 5     codeWriter.emit("$T $L", type, name);
 6     if (!initializer.isEmpty()) {
 7       codeWriter.emit(" = ");
 8       codeWriter.emit(initializer);
 9     }
10     codeWriter.emit(";\n");
11   }

這段程式碼是在生成變數時呼叫的。第二行是寫javadoc註釋,第三行是寫註解,第四行是寫訪問修飾符,第五行是寫該變數的型別和變數名,第6-9行是判斷變數是否有初始化值,如果有就寫初始化值

第10行;代表該變數寫出完畢。至此,HelloWorld.java的生成過程分析完畢。

 

用到的一些集合類中的方法:

Collections.singletonList

Collections.emptyList()

EnumSet.copyOf(Set<Modifier> modifiers)

Collections.unmodifiableList(new ArrayList<>(collection));   返回指定列表的不可修改檢視。 此方法允許模組為使用者提供對內部列表的“只讀”訪問許可權。

相關文章