AbstractProcessor can be used to design functionality like what lombok does. In my code, I have annotation as below:
@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface OnTransform {
}
Then I make a class and have this annotation annotated on my methods as below:
public class TargetClass {
@OnTransform
public int add(int a, int b) {
return a + b;
}
@OnTransform
public void say(String name) {
System.out.println("Hello " + name);
}
}
Now I want to add try finally block for these annotated methods, So I make a new class extends AbstractProcessor class and complete my code as below(actually this part of code mainly comes from ChatGPT):
public class TransformProcessor extends AbstractProcessor {
/**
* 初始化
* @param processingEnv environment to access facilities the tool framework
* provides to the processor
*/
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
Context context = ((JavacProcessingEnvironment) this.processingEnv).getContext();
this.elementUtils = (JavacElements) this.processingEnv.getElementUtils();
this.messager = this.processingEnv.getMessager();
this.names = Names.instance(context);
this.trees = JavacTrees.instance(this.processingEnv);
this.treeMaker = TreeMaker.instance(context);
}
/**
* 用于在编译器打印消息的组件
*/
private Messager messager;
/**
* 用于创建标识符的对象
*/
private Names names;
/**
* 语法树
*/
private JavacTrees trees;
/**
* trees 和 elementUtils都可以获取 元素的JCTree对象
*/
private JavacElements elementUtils;
/**
* 用来构造语法树节点
*/
private TreeMaker treeMaker;
/**
* 字段的语法树节点的集合
*/
private List<JCTree.JCMethodDecl> methodDecls;
/**
* 允许支持的源码版本
* @return
*/
@Override
public SourceVersion getSupportedSourceVersion() {
if (SourceVersion.latest().compareTo(SourceVersion.RELEASE_8) > 0) {
return SourceVersion.latest();
} else {
return SourceVersion.RELEASE_8;
}
}
/**
* 允许支持的注解类型
* @return
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
return ImmutableSet.of(OnTransform.class.getCanonicalName());
}
/**
* 代码增强处理
* @param annotations the annotation types requested to be processed
* @param roundEnv environment for information about the current and prior round
* @return
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
Set<? extends Element> set = roundEnv.getElementsAnnotatedWith(OnTransform.class);
set.forEach(element -> {
JCTree jcTree = trees.getTree(element);
jcTree.accept(new TreeTranslator() {
@Override
public void visitMethodDef(JCTree.JCMethodDecl method) {
// 运行父类方法
super.visitMethodDef(method);
// 获取方法体
JCTree.JCBlock body = method.body;
// 创建try语句块
JCTree.JCBlock tryBlock = createTryBlock(body);
// 创建finally语句块
JCTree.JCBlock finallyBlock = createFinallyBlock();
// 创建try-catch-finally语句块
JCTree.JCTry tryFinally = createTryCatchFinally(tryBlock, finallyBlock);
// 替换原来的方法体
method.body = treeMaker.Block(0, List.of(tryFinally));
}
});
});
return true;
}
//create try block
private JCTree.JCBlock createTryBlock(JCTree.JCBlock body) {
return treeMaker.Block(0, body.getStatements());
}
// create finally block
private JCTree.JCBlock createFinallyBlock() {
return treeMaker.Block(0, List.nil());
}
// create try-finally block
private JCTree.JCTry createTryCatchFinally(JCTree.JCBlock tryBlock, JCTree.JCBlock finallyBlock) {
JCTree.JCTry aTry = treeMaker.Try(tryBlock, List.nil(), finallyBlock);
return aTry;
}
}
I can have my project compiled successfully, but the .class file seems modified fail as below:
public class TargetClass {
public TargetClass() {
}
public int add(int a, int b) {
return a + b;
}
public void say(String name) {
System.out.println("Hello " + name);
}
}
I don't know why, I debuged the code and found that the method.body is changed , but don't know why it is not correctly flushed into .class file. Below code does not work as expected:
method.body = treeMaker.Block(0, List.of(tryFinally));
Maybe I used it wrong?
solved.
In finally block, if there are no codes in it, the compiler will ignore the try finally block because of no use. In order to let try finally block effect, we need to add some code into createFinallyBlock method such as System.out.println like below:
private JCTree.JCBlock createFinallyBlock() {
// 创建空finally语句块,由于没有任何作用,将会被编译器忽略添加
//return treeMaker.Block(0, List.nil());
// 创建带有语句的finally块
JCTree.JCIdent outIdent = treeMaker.Ident(names.fromString("out"));
JCTree.JCFieldAccess printStreamAccess = treeMaker.Select(outIdent, names.fromString("println"));
JCTree.JCFieldAccess systemAccess = treeMaker.Select(treeMaker.Ident(names.fromString("System")), names.fromString("out"));
JCTree.JCFieldAccess qualifiedAccess = treeMaker.Select(systemAccess, names.fromString("println"));
// 创建finally语句块
JCTree.JCExpressionStatement printStatement = treeMaker.Exec(
treeMaker.Apply(List.nil(),
qualifiedAccess,
List.of(treeMaker.Literal("finally executed"))));
return treeMaker.Block(0, List.of(printStatement));
}
Using JCTree.JCIdent to import java static class which will be ok.