使用 jdk.compiler 模块获取注释的 Class<?> 值
Get value as Class<?> for an Annotation using the jdk.compiler module
我正在使用启用了预览功能的 jdk.compiler 模块和 Java 15 编写一个 Java 编译器插件。根据 Oracle 的说法,Java 9 中引入的 jdk.compiler 模块包含我们在 java 8 的 tools.jar 中使用的所有 API,但 com.sun.tools.javac.tree 包除外.我想要做的是找到所有用我的注释注释的 类 并获取 value() 属性,在这种情况下类型为 Class>.
插件代码:
public class ExtensionMethodPlugin implements Plugin{
@Override
public String getName() {
return "ExtensionMethodPlugin";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(new TaskListener() {
@Override
public void started(TaskEvent event) {
}
@Override
public void finished(TaskEvent event) {
if (event.getKind() != TaskEvent.Kind.PARSE) {
return;
}
event.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitClass(ClassTree node, Void aVoid) {
var trees = Trees.instance(task);
var targetClass = InstrumentationUtils.findExtensionTargetForClass(node, trees, event.getCompilationUnit());
if (targetClass.isEmpty()) {
return super.visitClass(node, aVoid);
}
var methods = node
.getMembers()
.stream()
.filter(e -> e instanceof MethodTree)
.map(e -> (MethodTree) e)
.filter(tree -> tree.getReturnType() != null)
.filter(e -> InstrumentationUtils.shouldInstrumentMethod(e, targetClass.get()))
.collect(Collectors.toList());
throw new RuntimeException("Not implemented");
}
}, null);
}
});
}
}
InstrumentationUtils 代码:
@UtilityClass
public class InstrumentationUtils {
public Optional<Class<?>> findExtensionTargetForClass(ClassTree node, Trees trees, CompilationUnitTree tree) {
return node
.getModifiers()
.getAnnotations()
.stream()
.filter(a -> Extension.class.getSimpleName().equals(a.getAnnotationType().toString()))
.findFirst()
.map(e -> InstrumentationUtils.findConstantTargetClassInAnnotationTree(e, trees, tree));
}
private Class<?> findConstantTargetClassInAnnotationTree(AnnotationTree tree, Trees trees, CompilationUnitTree compilationUnitTree) {
return tree
.getArguments()
.stream()
.filter(entry -> entry.getKind() == Tree.Kind.ASSIGNMENT)
.findFirst()
.map(e -> (AssignmentTree) e)
.map(e -> classForName(e, trees, compilationUnitTree))
.orElseThrow(() -> new RuntimeException("Cannot compile: Missing constant class target in annotation!"));
}
private Class<?> classForName(AssignmentTree tree, Trees trees, CompilationUnitTree compilationUnitTree){
//Don't know how to move from here
}
}
注释代码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Extension {
Class<?> value();
}
我发现的唯一解决方案涉及使用 JCTree 类,但正如我之前所说,这些解决方案不再可用。我知道我可以很容易地在 jdk.compiler 模块中强制导出该包,但我更愿意找到更好的方法。我试过使用 TreeScanner,但这无济于事:
tree.accept(new TreeScanner<Class<?>, Void>() {
// b.class
@Override
public Class<?> visitAssignment(AssignmentTree node, Void unused) {
return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
//.class
@Override
public Class<?> visitMemberSelect(MemberSelectTree node, Void unused) {
return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
// class ???
@Override
public Class<?> visitIdentifier(IdentifierTree node, Void unused) {
trees.printMessage(Diagnostic.Kind.WARNING, node.getName(), tree, compilationUnitTree);
return super.visitIdentifier(node, unused);
}
}, null);
}
}, null);
}
}, null);
提前致谢!
当你需要做的就是“找到所有用我的注释注释的类并获取value()属性”,那么你就不需要树API 完全来自 jdk.compiler
模块,java.compiler
模块中包含的注释处理器 API 就足够了,而且处理起来更简单。
这是一个独立的例子:
static final String EXAMPLE_CODE =
"import java.lang.annotation.*;\n" +
"\n" +
"@Target({ElementType.TYPE})\n" +
"@Retention(RetentionPolicy.SOURCE)\n" +
"@interface Extension {\n" +
" Class<?> value();\n" +
"}\n" +
"\n" +
"@Extension(Runnable.class)\n" +
"public class SomeClass {\n" +
" public static void aMethod() {\n" +
" }\n" +
"\n" +
" @Extension(SomeClass.class)\n" +
" class NestedClassToMakeItMoreExciting {\n" +
" \n" +
" }\n" +
"}";
public static void main(String[] args) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager=compiler.getStandardFileManager(null,null,null);
Path tmp = Files.createTempDirectory("compile-test-");
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Set.of(tmp.toFile()));
Set<JavaFileObject> srcObj = Set.of(new SimpleJavaFileObject(
URI.create("string:///SomeClass.java"), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return EXAMPLE_CODE;
}
});
JavaCompiler.CompilationTask task
= compiler.getTask(null, fileManager, null, null, null, srcObj);
task.setProcessors(Set.of(new MyProcessor()));
task.call();
}
@SupportedAnnotationTypes("Extension")
@SupportedSourceVersion(SourceVersion.RELEASE_14)
static class MyProcessor extends AbstractProcessor {
@Override
public boolean process(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
TypeElement ext = processingEnv.getElementUtils().getTypeElement("Extension");
for(Element annotatedElement: roundEnv.getElementsAnnotatedWith(ext)) {
System.out.println(annotatedElement);
for(AnnotationMirror am: annotatedElement.getAnnotationMirrors()) {
if(am.getAnnotationType().asElement() == ext) {
am.getElementValues().forEach((e,v) ->
System.out.println("\t"+e.getSimpleName()+" = "+v.getValue()));
}
}
}
return true;
}
}
SomeClass
value = java.lang.Runnable
SomeClass.NestedClassToMakeItMoreExciting
value = SomeClass
当然,此功能可以在需要时与 jdk.compiler
扩展中的功能结合使用。
我正在使用启用了预览功能的 jdk.compiler 模块和 Java 15 编写一个 Java 编译器插件。根据 Oracle 的说法,Java 9 中引入的 jdk.compiler 模块包含我们在 java 8 的 tools.jar 中使用的所有 API,但 com.sun.tools.javac.tree 包除外.我想要做的是找到所有用我的注释注释的 类 并获取 value() 属性,在这种情况下类型为 Class>.
插件代码:
public class ExtensionMethodPlugin implements Plugin{
@Override
public String getName() {
return "ExtensionMethodPlugin";
}
@Override
public void init(JavacTask task, String... args) {
task.addTaskListener(new TaskListener() {
@Override
public void started(TaskEvent event) {
}
@Override
public void finished(TaskEvent event) {
if (event.getKind() != TaskEvent.Kind.PARSE) {
return;
}
event.getCompilationUnit().accept(new TreeScanner<Void, Void>() {
@Override
public Void visitClass(ClassTree node, Void aVoid) {
var trees = Trees.instance(task);
var targetClass = InstrumentationUtils.findExtensionTargetForClass(node, trees, event.getCompilationUnit());
if (targetClass.isEmpty()) {
return super.visitClass(node, aVoid);
}
var methods = node
.getMembers()
.stream()
.filter(e -> e instanceof MethodTree)
.map(e -> (MethodTree) e)
.filter(tree -> tree.getReturnType() != null)
.filter(e -> InstrumentationUtils.shouldInstrumentMethod(e, targetClass.get()))
.collect(Collectors.toList());
throw new RuntimeException("Not implemented");
}
}, null);
}
});
}
}
InstrumentationUtils 代码:
@UtilityClass
public class InstrumentationUtils {
public Optional<Class<?>> findExtensionTargetForClass(ClassTree node, Trees trees, CompilationUnitTree tree) {
return node
.getModifiers()
.getAnnotations()
.stream()
.filter(a -> Extension.class.getSimpleName().equals(a.getAnnotationType().toString()))
.findFirst()
.map(e -> InstrumentationUtils.findConstantTargetClassInAnnotationTree(e, trees, tree));
}
private Class<?> findConstantTargetClassInAnnotationTree(AnnotationTree tree, Trees trees, CompilationUnitTree compilationUnitTree) {
return tree
.getArguments()
.stream()
.filter(entry -> entry.getKind() == Tree.Kind.ASSIGNMENT)
.findFirst()
.map(e -> (AssignmentTree) e)
.map(e -> classForName(e, trees, compilationUnitTree))
.orElseThrow(() -> new RuntimeException("Cannot compile: Missing constant class target in annotation!"));
}
private Class<?> classForName(AssignmentTree tree, Trees trees, CompilationUnitTree compilationUnitTree){
//Don't know how to move from here
}
}
注释代码:
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.SOURCE)
public @interface Extension {
Class<?> value();
}
我发现的唯一解决方案涉及使用 JCTree 类,但正如我之前所说,这些解决方案不再可用。我知道我可以很容易地在 jdk.compiler 模块中强制导出该包,但我更愿意找到更好的方法。我试过使用 TreeScanner,但这无济于事:
tree.accept(new TreeScanner<Class<?>, Void>() {
// b.class
@Override
public Class<?> visitAssignment(AssignmentTree node, Void unused) {
return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
//.class
@Override
public Class<?> visitMemberSelect(MemberSelectTree node, Void unused) {
return node.getExpression().accept(new TreeScanner<Class<?>, Void>(){
// class ???
@Override
public Class<?> visitIdentifier(IdentifierTree node, Void unused) {
trees.printMessage(Diagnostic.Kind.WARNING, node.getName(), tree, compilationUnitTree);
return super.visitIdentifier(node, unused);
}
}, null);
}
}, null);
}
}, null);
提前致谢!
当你需要做的就是“找到所有用我的注释注释的类并获取value()属性”,那么你就不需要树API 完全来自 jdk.compiler
模块,java.compiler
模块中包含的注释处理器 API 就足够了,而且处理起来更简单。
这是一个独立的例子:
static final String EXAMPLE_CODE =
"import java.lang.annotation.*;\n" +
"\n" +
"@Target({ElementType.TYPE})\n" +
"@Retention(RetentionPolicy.SOURCE)\n" +
"@interface Extension {\n" +
" Class<?> value();\n" +
"}\n" +
"\n" +
"@Extension(Runnable.class)\n" +
"public class SomeClass {\n" +
" public static void aMethod() {\n" +
" }\n" +
"\n" +
" @Extension(SomeClass.class)\n" +
" class NestedClassToMakeItMoreExciting {\n" +
" \n" +
" }\n" +
"}";
public static void main(String[] args) throws IOException {
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager=compiler.getStandardFileManager(null,null,null);
Path tmp = Files.createTempDirectory("compile-test-");
fileManager.setLocation(StandardLocation.CLASS_OUTPUT, Set.of(tmp.toFile()));
Set<JavaFileObject> srcObj = Set.of(new SimpleJavaFileObject(
URI.create("string:///SomeClass.java"), JavaFileObject.Kind.SOURCE) {
@Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return EXAMPLE_CODE;
}
});
JavaCompiler.CompilationTask task
= compiler.getTask(null, fileManager, null, null, null, srcObj);
task.setProcessors(Set.of(new MyProcessor()));
task.call();
}
@SupportedAnnotationTypes("Extension")
@SupportedSourceVersion(SourceVersion.RELEASE_14)
static class MyProcessor extends AbstractProcessor {
@Override
public boolean process(
Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
TypeElement ext = processingEnv.getElementUtils().getTypeElement("Extension");
for(Element annotatedElement: roundEnv.getElementsAnnotatedWith(ext)) {
System.out.println(annotatedElement);
for(AnnotationMirror am: annotatedElement.getAnnotationMirrors()) {
if(am.getAnnotationType().asElement() == ext) {
am.getElementValues().forEach((e,v) ->
System.out.println("\t"+e.getSimpleName()+" = "+v.getValue()));
}
}
}
return true;
}
}
SomeClass
value = java.lang.Runnable
SomeClass.NestedClassToMakeItMoreExciting
value = SomeClass
当然,此功能可以在需要时与 jdk.compiler
扩展中的功能结合使用。