【问题标题】:Java static metaprogrammingJava 静态元编程
【发布时间】:2012-01-13 07:44:48
【问题描述】:

我想实现注释处理器,它将基于现有的“原型”类生成新类。

import java.util.List

@MyAnnotation
class MySuperClassPrototype {
    static MySuperClassPrototype createInstance() {
      return new MySuperClassPrototype();
    }
}

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

import java.util.List

class MySuperClass {
    static MySuperClass createInstance() {
      return new MySuperClass();
    }
    public void specialAddedMethod() {
      /*...*/
    }
}

我想复制所有顶级导入语句和静态成员,而不是原型类的静态成员。我已经使用 Compiler Tree API (com.sun.source.tree) 走了很远。我可以打印出 Tree 数据类型,同时用新的类名替换旧的类名。但是有些问题似乎很难。

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

可行吗?

同样,我需要过滤掉@MyAnnotation 注释,再次用 Tree.Kind.IDENTIFIER 或 Tree.Kind.MEMBER_SELECT 表示。

如何找出这个标识符引用的实际注释类?

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

你可以看到code I've come with here

【问题讨论】:

    标签: java annotations metaprogramming abstract-syntax-tree


    【解决方案1】:

    8 年了,还没有回答。正因为如此,我会尽力回答,让您满意。

    我更专注于问题的静态部分。

    TL;DR

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

    可行吗?

    是的,绝对的。

    如何找出这个标识符引用的实际注释类?

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

    静态元编程

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

    Pfeh,有很多东西可以吸收。如果您对此主题感兴趣,或多或少的好来源是wikipedia

    您的目标是在编译时生成一个类。对于运行时,这将通过cglib 之类的方式完成。但是,既然你选择了静态(和for all the right reasons),我就不解释了。

    您正在寻找的概念是annotation processor。该链接是指向 Baeldung 的链接,它们在哪里执行,您正在寻找什么,只考虑构建器模式。您会很高兴听到这种情况非常受鼓励,并且使用注释处理器 API 很容易实现。它甚至允许您生成代码,然后再次将其传递给相同或另一个注释处理器,而无需您执行任何操作。

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

    使用注释处理器

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

    @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;
        }
    }
    

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

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

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

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

    StackOverflow 中的答案确实不适合这个话题。我强烈建议查看Baeldung 示例并尝试从那里发现问题。这个 API 与 Java 6 一样古老,但仍然没有被广泛使用。我鼓励你,读者,自己尝试一下:)

    【讨论】:

      【解决方案2】:

      看看https://github.com/rzwitserloot/lombok/,它添加了你描述的方法。 比如

      • @Getter 基于字段添加getter方法
      • @Setter
      • @ToString 根据字段添加toString() 方法

      【讨论】:

        【解决方案3】:

        是的,这是可能的,我至少知道两种方法。

        首先,“传统”方式是编写 ant task/maven 插件/只是命令行 java 实用程序,它扫描给定的文件路径并为每个类调用类似 Class.forName(className).getAnnotations(MyAnnotation.class) 的东西。如果这不是 null 使用反射发现类并做你需要的。

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

        请查看此project 作为参考。

        【讨论】:

        • 这里只是一个小问题:静态元编程是编译时的元编程。纯粹从技术上讲,第一种“传统方式”是动态元编程(因为您正在启动运行时),但这只是一个愚蠢的小挑剔:)
        • 我将动态元编程描述为发生在与目标程序相同的运行单元中。在编译目标程序之前执行静态生成编译步骤仍然是“在编译时”,因此不是动态的。请注意,也可以进行类型安全的动态生成。
        猜你喜欢
        • 2010-09-11
        • 2012-01-06
        • 1970-01-01
        • 1970-01-01
        • 2010-11-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多