文章目录

Java 注解处理器:APT 与自定义注解

发布于 2026-04-07 22:23:09 · 浏览 7 次 · 评论 0 条

Java 注解处理器:APT 与自定义注解

APT(Annotation Processing Tool)是 Java 编译器提供的一种工具,用于在编译期扫描和处理注解,从而生成新的源代码或辅助文件。通过 APT,你可以将繁琐的重复代码(如 ButterKnife、Dagger 的生成逻辑)交给机器自动完成,减少手写代码的工作量。


一、 核心流程原理

理解 APT 的工作机制有助于后续编写代码。其核心逻辑发生在 Java 源代码编译成字节码之前。

graph LR A["Java Source Code: .java files"] --> B["Java Compiler"] B --> C{"Is there an annotation?"} C -- No --> D["Generate .class files"] C -- Yes --> E["Annotation Processing Tool"] E --> F["Analyze Elements"] F --> G["Generate New Source Code: .java files"] G --> B E --> H["Generate Metadata: .xml or .txt"]

二、 创建自定义注解

首先定义一个注解,标记在代码中告诉处理器哪些类需要被处理。

  1. 新建 一个名为 annotations 的 Java 模块(或者在你的项目中新建包)。
  2. 创建 一个名为 BindView 的接口文件。
  3. 编写 代码如下,定义注解的生命周期和作用目标:
package com.example.apt;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * TARGET: 定义注解可以用在什么地方(这里是字段上)
 * RETENTION: 定义注解保留到什么时候(这里是源码期,编译后丢弃)
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.SOURCE)
public @interface BindView {
    int value();
}

三、 实现注解处理器

处理器负责扫描带有 BindView 注解的代码,并根据注解信息生成新的 Java 文件。

  1. 新建 一个名为 compiler 的 Java 模块。
  2. 打开 该模块的 build.gradle 文件。
  3. 添加 对 Java 库的支持以及必要的依赖:
apply plugin: 'java-library'

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // 引入刚才定义的注解模块
    implementation project(':annotations')
    // Google 提供的自动注册服务,简化注册步骤
    implementation 'com.google.auto.service:auto-service:1.0-1'
}

sourceCompatibility = "1.8"
targetCompatibility = "1.8"
  1. 创建 BindViewProcessor 类,继承 AbstractProcessor
  2. 重写 核心方法,实现生成逻辑:
package com.example.apt;

import com.google.auto.service.AutoService;
import java.io.IOException;
import java.io.Writer;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic;
import javax.tools.JavaFileObject;

// 自动注册注解处理器,无需手动创建 META-INF 文件
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // 初始化工具类,用于打印日志
        Messager messager = processingEnv.getMessager();
        messager.printMessage(Diagnostic.Kind.NOTE, "BindViewProcessor Init...");
    }

    // 指定支持的 Java 版本
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    // 指定该处理器支持的注解类型
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return Set.of(BindView.class.getCanonicalName());
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        // 遍历所有被 @BindView 注解标记的元素
        for (Element element : roundEnv.getElementsAnnotatedWith(BindView.class)) {
            // 获取被注解的变量名
            String variableName = element.getSimpleName().toString();
            // 获取被注解的变量所在的类名(包名+类名)
            String packageName = processingEnv.getElementUtils().getPackageOf(element).getQualifiedName().toString();
            String className = element.getEnclosingElement().getSimpleName().toString();

            // 获取注解中的值(即 View 的 ID)
            BindView bindView = element.getAnnotation(BindView.class);
            int id = bindView.value();

            // 生成新类的类名:原类名 + Binding
            String newClassName = className + "Binding";

            try {
                // 创建 Java 文件对象
                JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(packageName + "." + newClassName);

                // 获取写入流
                Writer writer = sourceFile.openWriter();

                // 拼接 Java 代码字符串
                writer.write("package " + packageName + ";\n\n");
                writer.write("public class " + newClassName + " {\n");
                writer.write("    public static void bind(" + className + " target) {\n");
                writer.write("        target." + variableName + " = target.findViewById(" + id + ");\n");
                writer.write("    }\n");
                writer.write("}\n");

                // **关闭** 流
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return true;
    }
}

四、 配置宿主项目并测试

为了让编译器找到你的处理器,需要在主 App 模块中引用刚才创建的 compiler 模块。

  1. 打开 主 App 模块的 build.gradle 文件。
  2. 添加 依赖配置:
dependencies {
    // ... 其他依赖

    // 依赖注解模块(编译时和运行时都可能用到,如果是 SOURCE 级别其实编译期即可)
    implementation project(':annotations')
    // 依赖编译器模块(仅在编译时需要)
    annotationProcessor project(':compiler')
}
  1. 重新构建 项目(点击 Android Studio 的 Build -> Rebuild Project)。
  2. 查看 生成的文件。构建成功后,在 build/generated/source/apt/... 目录下,你会看到类似 MainActivityBinding.java 的文件。

五、 使用生成的代码

现在你可以在 Activity 中直接调用生成的代码。

  1. 打开 MainActivity.java
  2. 使用 @BindView 标记成员变量。
public class MainActivity extends AppCompatActivity {

    @BindView(R.id.tv_hello)
    TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // 调用 APT 生成的绑定方法
        MainActivityBinding.bind(this);

        textView.setText("APT 运行成功!");
    }
}

六、 常见注解元数据说明

在自定义注解时,需要精确控制注解的行为。以下是最常用的两个元注解及其参数说明。

元注解 作用范围 常用参数与说明
@Target 限制注解可以用在程序的哪些元素上 ElementType.FIELD:字段<br>ElementType.METHOD:方法<br>ElementType.TYPE:类、接口(包括注解类型)或枚举声明
@Retention 定义注解被保留的时间长短 RetentionPolicy.SOURCE:仅源码保留,编译器忽略<br>RetentionPolicy.CLASS:编译期保留,JVM 运行时忽略(默认)<br>RetentionPolicy.RUNTIME:运行期保留,可通过反射获取

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文