apt是java编译期注解处理工具,通过jsr-269标准在javac阶段生成代码,能替代部分模板代码但需正确配置环境;lombok即基于此实现,但jdk 17+需额外--add-exports参数。

Java APT 是什么,它真能替代手动写模板代码?
APT(Annotation Processing Tool)不是运行时反射,也不是字节码增强,它是编译器在 javac 阶段调用的一套标准接口,允许你在源码编译完成前读取注解、生成新 Java 文件。Lombok 就是靠它把 @Data 展开成 getter/setter/toString 等方法——但注意:它不修改原有类,只生成额外的 .java 文件(或直接注入到 AST,取决于实现方式)。
常见错误现象:Cannot resolve symbol 报错,明明写了 @Data 却没看到生成的方法;或者 IDE 不识别,但 mvn compile 又成功——这通常是因为 IDE 没启用 annotation processor,或未正确配置 processor path。
- 必须在构建工具中显式启用:Maven 要配
maven-compiler-plugin的annotationProcessorPaths;Gradle 要用annotationProcessor依赖 scope - IDEA 默认关闭 APT 支持,需进
Settings > Build > Compiler > Annotation Processors手动勾选“Enable annotation processing” - Lombok 是个特例:它用的是 JSR-269 标准 +
javac内部 API(如com.sun.source.tree),所以部分 JDK 版本(如 JDK 17+)需要额外加--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
Lombok 的 @Data 到底生成了哪些东西?
@Data 不是魔法,它等价于同时加了 @Getter、@Setter、@ToString、@EqualsAndHashCode 和 @RequiredArgsConstructor(仅对 final 或 @NonNull 字段)。但它不会生成无参构造函数,除非你显式加 @NoArgsConstructor。
容易踩的坑:
立即学习“Java免费学习笔记(深入)”;
-
@Data对static字段或transient字段默认忽略 getter/setter,但toString()仍会包含它们(除非用@ToString.Exclude) -
hashCode()和equals()默认只基于非静态字段,但若字段类型本身没重写equals,生成的逻辑可能不符合预期 - 继承场景下:
@Data不会为父类字段生成 getter,也不会在toString中自动包含父类字段(需配合@EqualsAndHashCode(callSuper = true)等显式声明)
自己写一个简单 APT Processor,为什么编译后看不到生成的类?
最常卡在这一步:你写了 MyProcessor extends AbstractProcessor,也注册了 META-INF/services/javax.annotation.processing.Processor,但 process() 方法压根没被调用,或者生成的 .java 文件没出现在 target/generated-sources/annotations 下。
核心原因只有两个:
- processor 没被
javac扫描到:确保 jar 包里有正确的META-INF/services/javax.annotation.processing.Processor文件,且内容是完整类名(如com.example.MyProcessor),末尾不能有多余空格或换行 - 生成路径不对:必须用
Filer.createSourceFile(),不能用new File().write();否则文件虽存在,但不会被编译器识别为源码参与后续编译 - 如果想生成类 A,并让它被当前模块其他类引用,必须保证生成时机早于引用它的类——即 APT 必须在主源码编译前完成,且生成路径要加入 source roots(Maven 插件会自动处理,但纯命令行
javac需手动加-s参数指定输出目录)
APT 和 Lombok 在 JDK 17+ 上为啥经常报错?
根本矛盾在于:JDK 9 引入模块系统后,com.sun.* 包被默认封装,而 Lombok 重度依赖 com.sun.tools.javac.tree 和 com.sun.tools.javac.util 这些内部 API。JDK 17 进一步收紧,默认禁止反射访问这些包。
典型错误信息:java.lang.IllegalAccessError: class lombok.javac.apt.LombokProcessor 或 module java.base does not "opens java.lang" to unnamed module。
- 解决方案不是降级 JDK,而是启动参数加白名单:Maven 命令需带
-DjvmArgs="--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED --add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED" - IntelliJ 用户还要在
Settings > Build > Compiler > Java Compiler里填上同样的Additional command line parameters - Gradle 用户可在
compileJavatask 的options.forkOptions.jvmArgs中设置,但要注意:这个参数只对 forked 编译生效,非 fork 模式下无效
真正麻烦的从来不是写注解或生成逻辑,而是让整个链路在不同 JDK、不同构建环境、不同 IDE 下稳定触发——哪怕只是多了一个空格,或少一个 --add-exports,就卡住。










