从Java注解处理器中访问源代码

22

我正在尝试从Java注解处理器中访问类型的实际原始源代码。是否有可能做到这一点?谢谢!

5个回答

26

我曾遇到一个问题,需要访问一些源代码(非String/基元常量的初始化器代码),通过使用编译器树API访问源代码解决了该问题。

以下是一般步骤:

1. 创建自定义TreePathScanner:

private static class CodeAnalyzerTreeScanner extends TreePathScanner<Object, Trees> {

private String fieldName;

private String fieldInitializer;

public void setFieldName(String fieldName) {
    this.fieldName = fieldName;
}

public String getFieldInitializer() {
    return this.fieldInitializer;
}

@Override
public Object visitVariable(VariableTree variableTree, Trees trees) {
    if (variableTree.getName().toString().equals(this.fieldName)) {
        this.fieldInitializer = variableTree.getInitializer().toString();
    }

    return super.visitVariable(variableTree, trees);
}

2. 在你的AbstractProcessor中,通过覆盖init方法保存当前编译树的引用:

@Override
public void init(ProcessingEnvironment pe) {
    super.init(pe);
    this.trees = Trees.instance(pe);
}

3. 获取VariableElement(在您的情况下是枚举)的初始化源代码:

// assuming theClass is a javax.lang.model.element.Element reference
// assuming theField is a javax.lang.model.element.VariableElement reference
String fieldName = theField.getSimpleName().toString();
CodeAnalyzerTreeScanner codeScanner = new CodeAnalyzerTreeScanner();
TreePath tp = this.trees.getPath(theClass);

codeScanner.setFieldName(fieldName);
codeScanner.scan(tp, this.trees);
String fieldInitializer = codeScanner.getFieldInitializer();

就这样!最终,fieldInitiliazer变量将包含用于初始化常量的确切代码行。通过一些调整,您应该能够使用相同的方法来访问源树中其他元素类型的源代码(即方法、包声明等)。

有关更多阅读和示例,请查看文章:使用Java 6 API进行源代码分析


9

这是@AdrianoNobre的答案的简单改编,参见此处。它更好地反映了访问者模式的预期用法。

AbstractProcessor init:

private Trees trees;

@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
    super.init(processingEnv);
    trees = Trees.instance(processingEnv);
}

MethodScanner

private static class MethodScanner extends TreePathScanner<List<MethodTree>, Trees> {
    private List<MethodTree> methodTrees = new ArrayList<>();

    public MethodTree scan(ExecutableElement methodElement, Trees trees) {
        assert methodElement.getKind() == ElementKind.METHOD;

        List<MethodTree> methodTrees = this.scan(trees.getPath(methodElement), trees);
        assert methodTrees.size() == 1;

        return methodTrees.get(0);
    }

    @Override
    public List<MethodTree> scan(TreePath treePath, Trees trees) {
        super.scan(treePath, trees);
        return this.methodTrees;
    }

    @Override
    public List<MethodTree> visitMethod(MethodTree methodTree, Trees trees) {
        this.methodTrees.add(methodTree);
        return super.visitMethod(methodTree, trees);
    }
}

使用扫描器获取方法体:

MethodScanner methodScanner = new MethodScanner();
MethodTree methodTree = methodScanner.scan(methodElement, this.trees);
methodTree.getBody();

methodTree.getBody() 有一个空的语句列表。我正在尝试按原样读取方法的主体。这可能吗? - krishan

2
快速回答是不可能的。
从Sun的SDK 5中用于注释处理的Mirror API JavaDoc中可以看出:
镜像API用于建模程序的语义结构。它提供了程序中声明的实体的表示形式,例如类、方法和字段。 方法级别以下的结构,如单个语句和表达式,没有被表示
Java 6注释处理基于一个新API,但它仍然不提供有关代码结构的更多细节。

2
Mirror API 是 Reflection API 的等效品,但在编译时期使用。使用此 API 无法读取方法的内部内容。其他任何事情都应该没问题。
如果你真的想要这样做,那么可能有一些技巧可以获得你想要读取的源文件的输入流。
  • Hibernate Metamodel Generator 使用 Filer.getResource() 读取 XML 文件,在 XmlParser.getInputStreamForResource() 中。问题是只支持 CLASS_OUTPUT 和 SOURCE_OUPUT,所以可能不适合你。

  • 另一个解决方案涉及找到源文件的路径,然后只需打开常规输入流即可。我曾经为 AndroidAnnotations 这种肮脏的 hack 在编译时读取 AndroidManifest.xml 文件。请参见 AndroidManifestFinder.findManifestFile()


2
我有一个关于第二个解决方案的问题。我怎么能找到某个类被我的自定义注解注释的源文件路径?你提供的链接实际上已经失效了。 - falconepl

2

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接