我正在尝试从 Java 注释处理器中访问一种类型的实际原始源代码。这有可能吗?谢谢!
5 回答
我遇到了一个问题,我必须访问一些源代码(非字符串/非原始常量的初始化程序代码)并通过Compiler Tree 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();
就是这样!最后 fieldInitializer 变量将包含用于初始化我的常量的确切代码行。通过一些调整,您应该能够使用相同的配方来访问源树中其他元素类型的源代码(即方法、包声明等)
如需更多阅读和示例,请阅读本文:使用 Java 6 API 进行源代码分析。
@AdrianoNobre 的答案的简单改编。它更好地反映了访问者模式的预期用途。
抽象处理器初始化:
private Trees trees;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
trees = Trees.instance(processingEnv);
}
方法扫描仪
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();
快速的回答是这是不可能的。
来自Sun SDK 5 中注释处理中使用的Mirror API JavaDoc :
Mirror API 用于对程序的语义结构进行建模。它提供程序中声明的实体的表示,例如类、方法和字段。 方法级别以下的构造,例如单独的语句和表达式,不会被表示。
Java 6 Annotation Processing 基于一个新的 API,但它仍然没有提供关于代码结构的更多细节。
Mirror API 等效于反射 API,但在编译时。无法使用此 API 读取方法的内部内容。其他任何事情都应该没问题。
如果您真的想这样做,那么可能会有一些技巧可以在您要读取的源文件上获取输入流。
Hibernate Metamodel Generator 在 XmlParser.getInputStreamForResource() 中使用 Filer.getResource() 读取 XML文件。问题是只支持 CLASS_OUTPUT 和 SOURCE_OUPUT,所以它可能不适合你。
另一种解决方案是找出源文件的路径,然后打开一个常规输入流。我已经为 AndroidAnnotations 做了这种肮脏的 hack,在编译时读取 AndroidManifest.xml 文件。请参阅AndroidManifestFinder.findManifestFile()。
你可以试试 Compiler Tree API ( http://download.oracle.com/javase/6/docs/jdk/api/javac/tree/index.html )
这个API被java编译器用来处理java的抽象语法树程式。它涉及到 Java 语言结构,例如语句、循环、表达式等。您可以在 JDK 目录中找到 jar 库(名为 tools.jar)