问题描述
我正在尝试从Java注释处理器中访问类型的实际原始源代码。这有可能吗?谢谢!
解决方法
我遇到一个问题,我必须访问一些源代码(非String / non-primitive常量的初始化程序代码),并通过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();
就是这样!最后,fieldInitiliazer变量将包含用于初始化我的常量的确切代码行。通过一些调整,您应该能够使用相同的配方来访问源代码树中其他元素类型的源代码(即方法,包声明等)
有关更多阅读和示例,请阅读本文:使用Java 6 API进行源代码分析。
, @AdrianoNobre的答案的简单改编。它更好地反映了访问者模式的预期用途。
AbstractProcessor初始化:
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();
, Mirror API与Reflection API等效,但是在编译时。无法使用此API读取方法的内部内容。其他都应该没问题。
如果您确实想执行此操作,则可能会有一些破解要在您要读取的源文件上获得输入流。
Hibernate Metamodel Generator使用XmlParser.getInputStreamForResource()中的Filer.getResource()读取XML文件。问题在于仅支持CLASS_OUTPUT和SOURCE_OUPUT,因此它可能不适合您。
另一种解决方案涉及找出源文件的路径,然后打开常规输入流。我对AndroidAnnotations进行了这种肮脏的破解,以便在编译时读取AndroidManifest.xml文件。请参阅AndroidManifestFinder.findManifestFile()。
, 快速答案是不可能的。
从Sun SDK 5的注释处理中使用的Mirror API JavaDoc:
Mirror API用于对
程序的语义结构。它
提供的表示
在程序中声明的实体,例如
作为类,方法和字段。
在方法级别以下进行构造,
例如个别陈述和
表达式,未表示。
Java 6注释处理基于新的API,但仍未提供有关代码结构的更多详细信息。
, 您可以尝试编译树API(http://download.oracle.com/javase/6/docs/jdk/api/javac/tree/index.html)
Java编译器使用此API来处理Java程序的抽象语法树。 Java语言构造(例如语句,循环,表达式等)一直到此为止。
您可以在JDK目录中找到jar库(名为tools.jar)