【问题标题】:Code replacement with an annotation processor使用注释处理器替换代码
【发布时间】:2012-11-21 07:59:55
【问题描述】:

我正在尝试编写annotation processor 以在类中插入方法和字段...而且文档非常稀少。我没有走远,我不知道我是否正确地接近它。

处理环境提供了一个Filer 对象,该对象具有创建新源文件和类文件的便捷方法。这些工作正常,但后来我试图弄清楚如何读取现有的源文件,它提供的只是“getResource”。所以在我的处理器实现中我已经这样做了:

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    try {
        for (TypeElement te : annotations) {
            for (Element element : roundEnv.getElementsAnnotatedWith(te)) {
                FileObject in_file = processingEnv.getFiler().getResource(
                    StandardLocation.SOURCE_PATH, "",
                    element.asType().toString().replace(".", "/") + ".java");

                FileObject out_file = processingEnv.getFiler().getResource(
                    StandardLocation.SOURCE_OUTPUT, "",
                    element.asType().toString().replace(".", "/") + ".java");

                //if (out_file.getLastModified() >= in_file.getLastModified()) continue;

                CharSequence data = in_file.getCharContent(false);

                data = transform(data); // run the macro processor

                JavaFileObject out_file2 = processingEnv.getFiler().createSourceFile(
                    element.asType().toString(), element);
                Writer w = out_file2.openWriter();
                w.append(data);
                w.close();
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
    }
    return true;
}

我的第一个困惑是我不禁觉得element.asType().toString().replace(".", "/") + ".java"(获取限定类型名称并将其转换为包和源文件路径)不是解决问题的好方法。 API 的其余部分设计过度,但似乎没有一种方便的方法来检索原始源代码。

真正的问题是编译器会自发地被输出目录中的第二个源文件(“错误:重复类”)弄乱,现在我被卡住了。

我已经编写了剩下的部分——一个宏词法分析器和解析器以及诸如此类的东西,用于计算一些数据并插入字段值和方法——但它作为编译器之外的初始步骤运行。除了原始文件不能具有 .java 扩展名(以防止编译器看到它们)这一事实之外,这很好用。然后听说注解可以做代码生成,我觉得会比较合适和方便,但是找不到太多的指导。

【问题讨论】:

标签: java code-generation annotation-processing


【解决方案1】:

注解处理器背后的意图是允许开发人员添加新类,而不是替换现有类。话虽如此,有一个错误允许您将代码添加到现有类。 Project Lombok 有 leveraged 用于将 getter 和 setter(除其他外)添加到已编译的 java 类中。

我用来“替换”方法/字段的方法是从输入类扩展或委托给输入类。这允许您覆盖/转移对目标类的调用。

如果这是你的输入类:

InputImpl.java:

public class InputImpl implements Input{
    public void foo(){
        System.out.println("foo");
    }
    public void bar(){
        System.out.println("bar");
    }
}

您可以生成以下内容来“替换”它:

InputReplacementImpl.java:

public class InputReplacementImpl implements Input{

    private Input delegate;

    //setup delegate....

    public void foo(){
        System.out.println("foo replacement");
    }
    public void bar(){
        delegate.bar();
    }
}

这引出了一个问题,你如何引用InputReplacementImpl 而不是InputImpl。您可以生成更多代码来执行包装,也可以简单地调用预期生成的代码的构造函数。

我不太确定你的问题是什么,但我希望这能对你的问题有所启发。

【讨论】:

  • 啊哈! 我想我以前见过龙目岛,这也是我没有理由认为这是不可能的部分原因。这个答案解释了编译器错误并给了我一些选择。目前,当简单的正则表达式为我进行代码查找和替换时,我不想陷入 AST hacks。在我的情况下,委派给班级并不是真正可行的,因为原始班级不完整且不可编译。据我所知,注解 API 的这个愚蠢的小限制意味着它对我没有用,尽管它很复杂。但现在我知道该怎么做了:不要使用注释!非常感谢您的帮助。
  • @JohnEricksen 我很好奇如何引用生成的类。要简单地使用构造函数,应先编译注解类。如果没有,IDE 将报告 no class def 错误。要包装,怎么做?
  • @yk42b,生成的类应该可以在 IDE 中引用,只要类路径设置正确并且注释处理器正在运行。
【解决方案2】:

有点晚了 :),但一种解决方案是使用 Byte Buddy 的转换器作为构建过程的一部分。参见例如https://github.com/raphw/byte-buddy/tree/master/byte-buddy-maven-plugin。

【讨论】:

    【解决方案3】:

    我不知道这个提示是否符合您的需求,但您的想法将与依赖注入结合使用,例如春天。

    1. 为类型和保留“SOURCE”创建自定义注释,Spring 将忽略该注释。
    2. 对注释处理器中的代码进行所需的更改,并将 Spring 的 @Component 注释添加到生成的 Java 类型中,并将其保存为原始类型的子类型,其类名与原始类型不同。李>
    3. 当 Spring 创建上下文时,它将根据您的处理结果而不是原始结果加载类。

    相反,Lombok 直接操作抽象源代码树,这比生成源代码复杂得多。这里的问题是,Java-Compiler-Module 被暴露(拼图)。

    另一种选择是使用像 ByteBuddy 这样的字节码 API,在运行时代理原始类。如果您不想为实例化类编写代码,这也需要依赖注入。这里的魔力是配置类中的类型集合的自动装配字段。让我知道,如果这对你感兴趣。然后我会在这里添加更多细节。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-13
      • 2020-09-13
      相关资源
      最近更新 更多