Java静态元编程

Vic*_*rov 5 java annotations metaprogramming abstract-syntax-tree

我想实现一个基于现有"prototype"类生成新类的注释处理器.

import java.util.List

@MyAnnotation
class MySuperClassPrototype {
    static MySuperClassPrototype createInstance() {
      return new MySuperClassPrototype();
    }
}
Run Code Online (Sandbox Code Playgroud)

由于下面的代码.将生成以下新源文件(编译单元):

import java.util.List

class MySuperClass {
    static MySuperClass createInstance() {
      return new MySuperClass();
    }
    public void specialAddedMethod() {
      /*...*/
    }
}
Run Code Online (Sandbox Code Playgroud)

我想复制所有顶级import语句和静态成员,而不是原型类的静态成员.我在Compiler Tree API(com.sun.source.tree)上走得很远.我可以打印出Tree数据类型,同时用new class name替换old.但是有些问题看起来很难.

如果我在树中获得Tree.Kind.IDENTIFIER,我如何找到它引用的实际类.我需要用MySuperClass标识符替换所有出现的MySuperClassPrototype标识符,然后打印出整个树.

这可行吗?

类似地,我需要过滤掉@MyAnnotation注释,然后再用Tree.Kind.IDENTIFIER或Tree.Kind.MEMBER_SELECT表示.

如何找到此标识符引用的实际注释类?

另一个问题是打印树.如果我使用toString方法,我得到了不错的结果,但是构造函数被打印为具有"<init>"名称的方法而不是与其类同名的方法,因此我需要手动打印每种树节点.

你可以看到我在这里附带的代码

Ale*_*exR 6

是的,这是可能的,我至少知道两种方式.

首先,"传统"方式是编写ant task/maven plugin/just命令行java实用程序,它扫描给定的文件路径并调用每个类的类似Class.forName(className).getAnnotations(MyAnnotation.class).如果这不是null,则使用反射发现类并执行您需要的操作.

其他方式有点困难,但更强大.您可以实现自己的Processor(实现javax.annotation.processing.Processor甚至更好的扩展javax.annotation.processing.AbstractProcessor.您的处理器只需要放在compiler类路径中,它将在编译器运行时自动运行.您甚至可以配置IDE(例如Eclipse)来运行处理器.对java编译器的一种扩展.因此,每次eclipse构建你的项目时,它运行处理器并根据你添加的新注释创建所有新类.

请看一下这个项目作为参考.


Tho*_*uck 1

8年了,还没回复。因此,我会尽力回答,让您满意。

我进一步关注问题的静态部分。

长话短说

您不会在此答案中找到复制和粘贴代码。

可行吗?

是的,一点没错。

如何找到该标识符引用的实际注释类?

您必须使用注释处理器中的 RoundEnvironment 来获取 TypeElement。

静态元编程

静态元编程(您要求的)是在编译时完成的元编程。作者:Kontrast:动态元编程是在运行时完成的元编程。元编程本身就是程序的设计,将其他程序作为数据处理。

Pfeh,有很多东西需要考虑。如果您对这个主题感兴趣,维基百科或多或少是一个不错的来源。

您的目标是在编译时生成一个类。对于运行时,这可以通过cglib之类的东西来完成。但是,由于您选择静态(并且出于所有正确的原因),我不会解释这一点。

您正在寻找的概念是注释处理器。该链接是 Baeldung 的链接,他们在那里完全按照您的需求进行操作,只考虑了构建器模式。您会很高兴听到,这种情况受到高度鼓励,并且可以使用注释处理器 API 轻松实现。它甚至允许您生成代码,该代码再次传递到同一个或另一个注释处理器,而无需您执行任何操作。

在开始之前,尝试用 google 搜索一下“Java Annotation Processing”。那里有很多好的资源,它们会对您有所帮助。太多了,在此一一列举。请注意,注释处理器中的编码与正常编码不同。差别不大,但您正在处理的类尚未创建。所以请记住这一点,不要灰心!

使用注释处理器

您的基本注释处理器将如下所示:

@SupportedAnnotationTypes("package.of.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, 
                           RoundEnvironment roundEnv) {
        // First let's find all annotated elements
        Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(MyAnnotation.class);
        // Handle all the annotated classes
        return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

AutoService Annotation用于动态注册您的注释处理器。它来自外部源,这样您就不会想知道为什么此代码无法编译。

在该handle all annotated classes部分中,您有带注释的元素(它们是带注释的类)。您现在必须验证它们是类而不是接口或其他注释。这是因为@Target(ElementType.Type)针对任何类型,包括接口和注释。此外,您需要验证您需要的任何内容是否存在,或者使用 Messager 将错误打印到编译

如果您在此处打印错误(例如),您将停止编译,并且在大多数现代 IDE 中都会看到该错误。可以通过拨打电话联系roundEnv.getMessager()

然后,您可以生成一个新类并将其作为 .java 文件写入编译器的输入。这可以通过使用Filer来完成。

StackOverflow 中的答案确实没有公正地对待这个主题。我强烈建议您查看Baeldung示例并尝试从中发现一些东西。这个 API 与 Java 6 一样古老,但仍然没有得到广泛使用。我鼓励读者您亲自尝试一下:)