meta programming
- 編譯時期 Processor
- 執行時期 Reflection, InvocationHandler
這邊主要討論編譯時期 JSR 269 javax.annotation.processing.AbstractProcessor
從常見的 ORM annotations 、json2pojo(Gson, Jackson) 、Dagger1/2、Mockito、Retrofit、AutoValue、AutoParcel、ButterKnife、AndroidAnnotations、RoboGuice 等函式庫,大量利用 annotations 來精簡聚焦、解決煩冗的例行性撰寫程序。
像是 AutoValue 就節省了煩冗的 getter 、 setter 、builder 等例行撰寫程序。
除了解析 annotations 還包括解析 abstract methods, methods, interfaces, fields 等,緊接著是產生 java file 的 template language 以及物件導向的產生器。
@Example
public class ExampleClass implements Runnable {
public final String name;
public ExampleClass(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
}
}
public ExampleProcessor extends AbstractProcessor {
// ...
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
Set<? extends Element> elements = env.getElementAnnotatedWith(Example.class);
for (Element e : elements) { // 可列出 @Example
System.out.println(e);
}
return false;
}
}
依據解析結果產生 java file。
Template Language:
Apache Velocity Template Language, *.vm
For AVTL example:
#if (!$pkg.empty)
package $pkg;
#end
#foreach ($i in $imports)
import $i;
#end
@${generated}("com.google.auto.value.processor.AutoAnnotationProcessor")
final class $className implements $annotationName {
## Fields
#foreach ($m in $members)
#if ($params.containsKey($m.toString()))
private final $m.type $m;
#else
private static final $m.type $m = $m.defaultValue;
#end
#end
Square JavaPoet: https://github.com/square/javapoet
For JavaPoet example:
package com.example.helloworld;
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();
JavaFile javaFile = JavaFile.builder("com.example.helloworld", helloWorld)
.build();
javaFile.emit(System.out);
Annotation annotation = ((DeclaredType) typeMirror).asElement().getAnnotation(AutoValue.Field.class);
https://github.com/yongjhih/JavaPoetic:
JavaFile javaFile = JavaFile.package("com.example.helloworld").class(
JavaClass.public().final().name("HelloWorld").method(
JavaMethod.public().static().void().name("main").parameter(String[].class, "args").statement(
JavaStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!"),
JavaStatement("$T.out.println($S)", System.class, "Hello, JavaPoet!")
)
)
)
);
APT, Annotation-Processing Tool
- ASM
- AspectJ
- Javasist
- https://speakerdeck.com/jakewharton/annotation-processing-boilerplate-destruction-square-waterloo-2014
- https://github.com/8tory/simple-parse (runtime)
- https://github.com/8tory/auto-parse (source)
- http://velocity.apache.org/engine/devel/vtl-reference-guide.html
- https://github.com/yongjhih/JavaPoetic
- https://github.com/yongjhih/RetroFacebook
- http://blog.retep.org/2009/02/13/getting-class-values-from-annotations-in-an-annotationprocessor/