简体   繁体   English

用注释处理器替换代码

[英]Code replacement with an annotation processor

I'm trying to write an annotation processor to insert methods and fields on a class... and the documentation is so sparse.我正在尝试编写一个注释处理器以在 class 上插入方法和字段......而且文档非常稀疏。 I'm not getting far and I don't know if I'm approaching it correctly.我没有走远,我不知道我是否正在正确地接近它。

The processing environment provides a Filer object which has handy methods for creating new source and class files.处理环境提供了一个Filer object,它具有创建新源和 class 文件的便捷方法。 Those work fine but then I tried to figure out how read the existing source files, and all it provides is "getResource".这些工作正常,但后来我试图弄清楚如何读取现有的源文件,它提供的只是“getResource”。 So in my Processor implementation I've done this:所以在我的处理器实现中,我这样做了:

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

My first quandary is I can't help feeling that element.asType().toString().replace(".", "/") + ".java" (to get the qualified type name and convert it into a package and source file path) is not a nice way to approach the problem.我的第一个窘境是我忍不住觉得element.asType().toString().replace(".", "/") + ".java" (获取限定类型名称并将其转换为 package 和源文件路径)不是解决问题的好方法。 The rest of the API is so over-engineered but there doesn't seem to be a handy method for retrieving the original source code. API 的 rest 设计过度,但似乎没有检索原始源代码的便捷方法。

The real problem is that then the compiler gets spontaneously upset by the second source file in the output directory ("error: duplicate class") and now I'm stuck.真正的问题是编译器会自发地被 output 目录中的第二个源文件打乱(“错误:重复类”),现在我被卡住了。

I've already written the rest of this -- a macro lexer and parser and whatnot for calculating some data and inserting the field values and methods -- but it operates as a initial step outside the compiler.我已经编写了 rest 这个——一个宏词法分析器和解析器以及诸如此类的东西,用于计算一些数据和插入字段值和方法——但它作为编译器之外的初始步骤运行。 Except for the fact that the original files cannot have a.java extension (to prevent the compiler seeing them), this works nicely.除了原始文件不能有 .java 扩展名(以防止编译器看到它们)这一事实外,这很好用。 Then I heard that annotations can do code generation, which I assume will be more proper and convenient, but I can't find much guidance on it.然后我听说注解可以生成代码,我觉得这样更合适也更方便,但是我找不到太多的指导。

The intention behind the annotation processor is to allow a developer to add new classes, not replace existing classes.注释处理器背后的意图是允许开发人员添加新类,而不是替换现有类。 That being said, there is a bug that allows you to add code to existing classes.话虽如此,但存在一个允许您向现有类添加代码的错误。 Project Lombok has leveraged this to add getter and setter (among other things) to your compiled java classes.龙目岛项目利用这一点将 getter 和 setter(以及其他东西)添加到您编译的 java 类中。

The approach I have taken to 'replace' methods/fields is either extend from or delegate to the input class.我用来“替换”方法/字段的方法是从输入类扩展或委托给输入类。 This allows you to override/divert calls to the target class.这允许您覆盖/转移对目标类的调用。

So if this is your input class:因此,如果这是您的输入类:

InputImpl.java: InputImpl.java:

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

You could generate the following to "replace" it:您可以生成以下内容来“替换”它:

InputReplacementImpl.java: 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();
    }
}

This begs the question, how do you reference InputReplacementImpl instead of InputImpl .这就引出了一个问题,你如何引用InputReplacementImpl而不是InputImpl You can either generate some more code to perform the wrapping or simply call the constructor of the code expected to be generated.您可以生成更多代码来执行包装,也可以简单地调用预期生成的代码的构造函数。

I'm not really sure what your question is, but I hope this sheds some light on your issues.我不太确定你的问题是什么,但我希望这能对你的问题有所了解。

A bit late :), but one solution could be to use Byte Buddy's transformer as part of the build process.有点晚了 :),但一种解决方案可能是使用 Byte Buddy 的转换器作为构建过程的一部分。 See eg https://github.com/raphw/byte-buddy/tree/master/byte-buddy-maven-plugin .参见例如https://github.com/raphw/byte-buddy/tree/master/byte-buddy-maven-plugin

I don't know if this hint matches your needs, but your idea will work in conjunction with dependency-injection eg Spring.我不知道这个提示是否符合您的需求,但您的想法将与依赖注入结合使用,例如 Spring。

  1. Create a custom annnotation for types and retention "SOURCE" which will be ignored by Spring.为类型和保留“SOURCE”创建自定义注释,Spring 将忽略它。
  2. Do the desired change to the code in your annotation-processor and add Spring's @Component Annotation to you generated Java-Type and save it as a subtype of the original type with a class-name different form the origifinal one.对注释处理器中的代码进行所需的更改,并将 Spring 的 @Component 注释添加到生成的 Java 类型中,并将其保存为原始类型的子类型,其类名与原始类型不同。
  3. When Spring creates the context, it will load the class based on your processing-result instead of the original one.当 Spring 创建上下文时,它将根据您的处理结果加载 class 而不是原始的。

In contrary, Lombok is manipulating the abstract source tree directly, which is much more complex then generating source-code.相反,Lombok 是直接操作抽象源代码树,这比生成源代码要复杂得多。 Problem here is, the Java-Compiler-Module is exposed (Jigsaw).这里的问题是,Java-Compiler-Module 暴露了(Jigsaw)。

Another alternative could be to use some bytecode-api like ByteBuddy, to proxy the original class at runtime.另一种选择是使用像 ByteBuddy 这样的字节码 API,在运行时代理原始的 class。 This will also require dependency-injection if you do not want to write code for instantiatig the class. Here the magic is an autowired field of type collection inside a Configuration class. Let me know, if this is interesting to you.如果您不想为实例化 class 编写代码,这也需要依赖注入。这里的魔法是配置 class 中类型集合的自动装配字段。如果您对此感兴趣,请告诉我。 Then I will add more details, here.然后我将在此处添加更多详细信息。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM