欢迎光临
我们一直在努力

如何利用 Java 编译器插件 JSR 269 在编译期自动生成样板代码教程

Java 的注解处理器(Annotation Processor,基于 JSR 269 标准)是编译期代码生成和分析的强大工具。它允许我们在代码编译阶段读取自定义注解,并根据这些注解动态创建新的 Java 源文件,从而彻底消除大量重复的样板代码(如 Builder、Service Factory 等)。

本教程将展示如何创建并注册一个简单的注解处理器,用于自动生成一个基于特定注解的 Greeter 类。

步骤一:定义自定义注解

我们首先定义一个源级别(SOURCE)的注解,这意味着它只在编译期可见,不会被编译进最终的 .class 文件中。

// package com.example.annotations
import java.lang.annotation.*;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface GenerateGreeter {
    String name() default "World";
}

步骤二:实现注解处理器

处理器是核心部分,它继承自 AbstractProcessor。我们需要关注两个关键点:指定支持的注解类型,以及在 process 方法中使用 Filer 来生成新文件。

// package com.example.processor
import com.example.annotations.GenerateGreeter;
import javax.annotation.processing.*;
import javax.lang.model.element.*;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Set;

@SupportedAnnotationTypes("com.example.annotations.GenerateGreeter")
@SupportedSourceVersion(javax.lang.model.SourceVersion.RELEASE_8)
public class GreeterProcessor extends AbstractProcessor {

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for (Element annotatedElement : roundEnv.getElementsAnnotatedWith(GenerateGreeter.class)) {

            // 确保我们只处理类或接口
            if (annotatedElement.getKind() != ElementKind.CLASS && annotatedElement.getKind() != ElementKind.INTERFACE) {
                continue;
            }

            // 获取注解值 (这里我们只使用默认值)
            GenerateGreeter annotation = annotatedElement.getAnnotation(GenerateGreeter.class);
            String greetingName = annotation.name();

            // 确定要生成的类名和包名
            String packageName = processingEnv.getElementUtils().getPackageOf(annotatedElement).getQualifiedName().toString();
            String generatedClassName = "AutoGreeter_" + annotatedElement.getSimpleName().toString();

            // 开始文件生成
            try {
                generateSourceFile(packageName, generatedClassName, greetingName);
            } catch (IOException e) {
                // 使用 Messager 报告错误
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "无法生成 Greeter 类: " + e.getMessage());
            }
        }
        // 返回 true 表示我们已经处理了这些注解
        return true;
    }

    private void generateSourceFile(String packageName, String className, String greetingName) throws IOException {
        // 使用 Filer 创建一个新的源文件
        JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + "." + className);

        try (PrintWriter writer = new PrintWriter(sourceFile.openWriter())) {
            writer.println("package " + packageName + ";");
            writer.println("");
            writer.println("public class " + className + " {");
            writer.println("    public static String greet() {");
            writer.println("        return \"Hello, " + greetingName + "! Generated at compile time.\";");
            writer.println("    }");
            writer.println("}");
        }
    }
}

步骤三:注册处理器

Java 编译器需要知道哪些类是注解处理器。这通过标准的 Service Provider Interface (SPI) 机制实现。在你的项目资源目录下(通常是 src/main/resources),创建以下路径和文件:

文件路径: META-INF/services/javax.annotation.processing.Processor

文件内容: 写入你处理器的完整类名。

# META-INF/services/javax.annotation.processing.Processor
com.example.processor.GreeterProcessor

步骤四:使用并测试

现在,我们创建一个被注解的类。假设我们的注解处理器模块已经被编译成 JAR,并在主项目中引用。

// package com.example.app
import com.example.annotations.GenerateGreeter;

@GenerateGreeter(name = "Compiler")
public class MyService {
    // 这是一个空类,但它的存在会触发代码生成
}

当我们编译 MyService.java 时,注解处理器会被激活,并在编译输出目录中生成一个新文件 AutoGreeter_MyService.java(包名为 com.example.app):

// 自动生成的代码 (AutoGreeter_MyService.java)
package com.example.app;

public class AutoGreeter_MyService {
    public static String greet() {
        return "Hello, Compiler! Generated at compile time.";
    }
}

现在,我们可以在应用代码中直接调用这个自动生成的类:

// 在主应用中调用
package com.example.app;

public class Main {
    public static void main(String[] args) {
        // 注意:这个类在编译前并不存在,但编译完成后就能找到它。
        System.out.println(AutoGreeter_MyService.greet()); 
    }
}
// 输出: Hello, Compiler! Generated at compile time.

通过 JSR 269,我们成功地将代码生成任务从运行时转移到了编译期,极大地简化了开发流程并保证了类型安全。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 如何利用 Java 编译器插件 JSR 269 在编译期自动生成样板代码教程
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址