AbstractProcessor 无法使用 JCTree 修改已存在的方法。

huangapple go评论73阅读模式
英文:

AbstractProcessor can not use JCTree to modify exist method

问题

AbstractProcessor 可用于设计类似于 Lombok 的功能。在您的代码中,我有以下的注解:

@Retention(RetentionPolicy.SOURCE)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface OnTransform {
}

然后,我创建了一个类,并在我的方法上使用了这个注解,如下所示:

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);
    }
}

现在,我想为这些带有注解的方法添加 try-finally 块,所以我创建了一个新的类,扩展了 AbstractProcessor 类,并完成了我的代码,如下所示:

public class TransformProcessor extends AbstractProcessor {
    // 其他代码...

    @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;
                    JCTree.JCBlock tryBlock = createTryBlock(body);
                    JCTree.JCBlock finallyBlock = createFinallyBlock();
                    JCTree.JCTry tryFinally = createTryCatchFinally(tryBlock, finallyBlock);
                    method.body = treeMaker.Block(0, List.of(tryFinally));
                }
            });
        });
        return true;
    }
    
    // 其他代码...
}

项目可以成功编译,但是 .class 文件似乎未正确修改,如下所示:

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);
    }
}

我不清楚为什么,我调试了代码并发现方法体 method.body 已更改,但不清楚为什么它未正确刷新到 .class 文件中。下面的代码没有按预期工作:

method.body = treeMaker.Block(0, List.of(tryFinally));

也许我使用它的方式不正确?

英文:

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(&quot;Hello &quot; + 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&lt;JCTree.JCMethodDecl&gt; methodDecls;

/**
 * 允许支持的源码版本
 * @return
 */
@Override
public SourceVersion getSupportedSourceVersion() {
    if (SourceVersion.latest().compareTo(SourceVersion.RELEASE_8) &gt; 0) {
        return SourceVersion.latest();
    } else {
        return SourceVersion.RELEASE_8;
    }
}

/**
 * 允许支持的注解类型
 * @return
 */
@Override
public Set&lt;String&gt; 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&lt;? extends TypeElement&gt; annotations, RoundEnvironment roundEnv) {
    Set&lt;? extends Element&gt; set = roundEnv.getElementsAnnotatedWith(OnTransform.class);
    set.forEach(element -&gt; {
        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(&quot;Hello &quot; + 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?

答案1

得分: 0

已解决。

在finally块中,如果没有代码,编译器会忽略try finally块,因为没有用处。
为了让try finally块生效,我们需要在createFinallyBlock方法中添加一些代码,例如System.out.println,如下所示:

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));
}

使用JCTree.JCIdent导入Java静态类将是可行的。

英文:

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(&quot;out&quot;));
    JCTree.JCFieldAccess printStreamAccess = treeMaker.Select(outIdent, names.fromString(&quot;println&quot;));
    JCTree.JCFieldAccess systemAccess = treeMaker.Select(treeMaker.Ident(names.fromString(&quot;System&quot;)), names.fromString(&quot;out&quot;));
    JCTree.JCFieldAccess qualifiedAccess = treeMaker.Select(systemAccess, names.fromString(&quot;println&quot;));

    // 创建finally语句块
    JCTree.JCExpressionStatement printStatement = treeMaker.Exec(
            treeMaker.Apply(List.nil(),
                    qualifiedAccess,
                    List.of(treeMaker.Literal(&quot;finally executed&quot;))));
    return treeMaker.Block(0, List.of(printStatement));
}

Using JCTree.JCIdent to import java static class which will be ok.

huangapple
  • 本文由 发表于 2023年6月15日 16:25:10
  • 转载请务必保留本文链接:https://go.coder-hub.com/76480530.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定