Square 出品,必属精品
没错,有事没事就要抱大腿,JavaPoet是以面向对象的方式来生成.java源代码的一个api库。
背景
在使用APT生产Java源码的过程中,我们通常的做法是手动写代码,但是这样确实不符合程序员的性格啊,毕竟手动填进去的代码,万一少个括号或者分号,肯定编译不过去,还要再改。
恩,JavaPoet因此应运而生。具体原理这里就不讲了,就是生产字符串信息,而其中的背景以及设计的思路确实是值得学习的。当时不知道这个类库的时候还傻傻的一行一行写字符串... (捂脸逃
实际应用
下面举个栗子:
public final class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, JavaPoet!");
}
其对应的生产方式代码如下:
//方法生产
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.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(main)
.build();
//.java 文件生产
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
当我们的类方法体里面并没有模板的方法,类型或者参数表达式,是固定不变的,我们可以这样写:
MethodSpec main = MethodSpec.methodBuilder("main")
.addCode(""
+ "int total = 0;\n"
+ "for (int i = 0; i < 10; i++) {\n"
+ " total += i;\n"
+ "}\n")
.build();
生产的代码是这样的:
void main() {
int total = 0;
for (int i = 0; i < 10; i++) {
total += i;
}
}
或者有参数是可变的,这样写:
private MethodSpec computeRange(String name, int from, int to, String op) {
return MethodSpec.methodBuilder(name)
.returns(int.class)
.addStatement("int result = 0")
.beginControlFlow("for (int i = " + from + "; i < " + to + "; i++)")
.addStatement("result = result " + op + " i")
.endControlFlow()
.addStatement("return result")
.build();
}
然后调用:
computeRange("multiply10to20", 10, 20, "*")
生产的代码是这样的:
int multiply10to20() {
int result = 0;
for (int i = 10; i < 20; i++) {
result = result * i;
}
return result;
}
以上大概是如何使用的一些栗子,然后我们来想一下这个api是如何实现的。
我要如何写出这样的被STAR 3500以上的API
哈哈,这个标题写的有点重了,不过还是大致说下:
- 坚持。 肯定不是一蹴而就的,这个项目被维护了两年,到现在还一直有提交。可是国内的某些githuber ,一次提交就完事了,以后再不更新,有人提isuue 也爱答不理,功利心太强。
- 想法很重要,行动更重要。这里说白了就是字符串拼接,但是人家发现了这个问题,并且切实的解决了这个问题,确实是高。Let me read the fucking code。
- java类结构分析
一个.java 文件,大致可以做一下切分如下图:
然后我们来看下注解是如何添加到方法,变量以及类上面的。
在FiledSpec中,通过一个建造者模式,添加一个AnnotationSpec
public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
checkArgument(annotationSpecs != null, "annotationSpecs == null");
for (AnnotationSpec annotationSpec : annotationSpecs) {
this.annotations.add(annotationSpec);
}
return this;
}
在MethodSpec 和 TypeSpec中,同样的:
public Builder addAnnotations(Iterable<AnnotationSpec> annotationSpecs) {
checkArgument(annotationSpecs != null, "annotationSpecs == null");
for (AnnotationSpec annotationSpec : annotationSpecs) {
this.annotations.add(annotationSpec);
}
return this;
}
在FliedSpec中生产代码:
void emit(CodeWriter codeWriter, Set<Modifier> implicitModifiers) throws IOException {
codeWriter.emitJavadoc(javadoc);
codeWriter.emitAnnotations(annotations, false);
codeWriter.emitModifiers(modifiers, implicitModifiers);
codeWriter.emit("$T $L", type, name);
if (!initializer.isEmpty()) {
codeWriter.emit(" = ");
codeWriter.emit(initializer);
}
codeWriter.emit(";\n");
}
这里我们分析下实现:可以看到,根据上面的图例,合理的抽象出对象,使用合理的设计模式,写的代码也易于理解。确实有很多值得借鉴和学习的地方。