深入浅出 Java 注解处理器:从原理到实战,一键生成代码
在日常 Java 开发中你一定用过Override、Autowired、Data这些注解它们极大简化了代码开发。但你有没有想过Lombok 是如何通过一个 Data 就自动生成 get/set 方法MyBatis-Plus 的代码生成器、Spring 的依赖注入底层都依赖什么技术实现答案就是Java 注解处理器Annotation Processor。它是 JDK 1.6 提供的编译期特性能在编译阶段扫描、处理注解自动生成代码、校验语法、修改字节码。本文将带你从零吃透注解处理器从核心原理到实战开发手把手实现一个自定义注解 注解处理器看完就能自己写代码生成工具一、注解处理器核心概念1. 什么是注解处理器插入广告各行各业学习千款源码就上svipm.com.cn注解处理器是运行在编译期的工具不属于运行时逻辑。它的工作流程编译器编译 Java 文件时扫描所有类上的注解匹配到自定义注解处理器后执行处理逻辑生成新的 Java 文件 / 字节码、打印日志、抛出编译异常等编译器继续编译生成的文件最终打包到 class 文件中。关键特性编译期执行不影响运行时性能无需反射无性能损耗可自动生成代码减少重复开发常用于框架底层Lombok、Spring、ButterKnife。2. 核心 API 与依赖开发注解处理器核心依赖两个模块自定义注解标记需要处理的类 / 方法 / 字段注解处理器继承AbstractProcessor重写处理逻辑SPI 配置让编译器找到自定义处理器关键。JDK 核心 APIAbstractProcessor所有处理器的父类Element编译期元素类、方法、字段、参数JavaFileObject生成 Java 源码的工具类ProcessingEnvironment编译期环境工具。二、注解处理器工作流程编译期一句话总结编译器启动 → 扫描注解 → 匹配处理器 → 执行处理 → 生成代码 → 完成编译。详细流程源码编译阶段.java→ 抽象语法树AST注解处理阶段处理器扫描 AST 上的注解逻辑处理解析注解参数生成新的 Java 文件循环处理新生成的文件也会被扫描直到无新文件生成最终编译所有源码编译为.class文件。注意注解处理器不能修改已有 Java 源码只能生成新的源码 / 字节码Lombok 看似修改源码本质是通过修改抽象语法树实现的高级操作。三、开发环境准备本文使用Maven构建项目分为两个模块annotation存放自定义注解processor存放注解处理器依赖 annotation 模块。为什么拆分模块因为注解处理器需要在编译期独立运行拆分后避免业务代码与处理器代码耦合。Maven 依赖配置1. annotation 模块纯注解无其他依赖xmldependencies !-- 无需运行时依赖 -- /dependencies2. processor 模块核心依赖xmldependencies !-- 依赖自定义注解模块 -- dependency groupIdcom.example/groupId artifactIdannotation/artifactId version1.0-SNAPSHOT/version /dependency !-- 编译期工具依赖 -- dependency groupIdcom.squareup/groupId artifactIdjavapoet/artifactId version1.13.0/version /dependency /dependencies build plugins !-- 编译JDK版本 -- plugin groupIdorg.apache.maven.plugins/groupId artifactIdmaven-compiler-plugin/artifactId version3.8.1/version configuration source8/source target8/target encodingUTF-8/encoding /configuration /plugin /plugins /buildJavaPoetSquare 开源的代码生成框架简化 Java 源码生成推荐使用。四、实战开发自定义注解处理器我们实现一个自动生成 Builder 构造器的注解处理器自定义AutoBuilder注解标记在实体类上编译期自动生成XXXBuilder类支持链式调用简化对象创建。步骤 1定义自定义注解在annotation模块创建AutoBuilderjava运行import java.lang.annotation.*; /** * 自动生成Builder模式的注解 * 作用目标类 * 生命周期编译期SOURCE运行时无需保留 */ Target(ElementType.TYPE) Retention(RetentionPolicy.SOURCE) public interface AutoBuilder { }Retention(SOURCE)注解只在源码阶段存在编译后丢弃节省资源Target(ElementType.TYPE)只能标记在类上。步骤 2开发注解处理器核心继承AbstractProcessor重写 4 个核心方法init()初始化获取环境工具process()核心处理方法扫描注解、生成代码getSupportedAnnotationTypes()指定支持的注解getSupportedSourceVersion()指定 JDK 版本。在processor模块创建AutoBuilderProcessorjava运行import com.example.annotation.AutoBuilder; import com.squareup.javapoet.*; import javax.annotation.processing.*; import javax.lang.model.SourceVersion; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; import javax.lang.model.util.Elements; import java.io.IOException; import java.util.Set; /** * 自定义注解处理器 * 处理AutoBuilder注解生成Builder类 */ SupportedSourceVersion(SourceVersion.RELEASE_8) public class AutoBuilderProcessor extends AbstractProcessor { // 元素工具类获取类、字段、方法信息 private Elements elementUtils; /** * 初始化方法 */ Override public synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils processingEnv.getElementUtils(); } /** * 核心处理方法 * param annotations 待处理的注解集合 * param roundEnv 编译环境 * return 是否处理完成 */ Override public boolean process(Set? extends TypeElement annotations, RoundEnvironment roundEnv) { // 1. 扫描所有被AutoBuilder标记的类 Set? extends Element elements roundEnv.getElementsAnnotatedWith(AutoBuilder.class); for (Element element : elements) { // 强转为类元素 TypeElement typeElement (TypeElement) element; // 获取类名 String className typeElement.getSimpleName().toString(); // 获取包名 String packageName elementUtils.getPackageOf(typeElement).getQualifiedName().toString(); // 2. 使用JavaPoet生成Builder类 generateBuilderClass(packageName, className, typeElement); } return true; } /** * 生成Builder模式代码 */ private void generateBuilderClass(String packageName, String className, TypeElement typeElement) { // 生成的类名User → UserBuilder String builderClassName className Builder; // 构建类 TypeSpec.Builder builder TypeSpec.classBuilder(builderClassName) .addModifiers(javax.lang.model.element.Modifier.PUBLIC); // 生成build()方法 MethodSpec buildMethod MethodSpec.methodBuilder(build) .addModifiers(javax.lang.model.element.Modifier.PUBLIC) .returns(ClassName.get(packageName, className)) .addStatement(return new $N(), className) .build(); builder.addMethod(buildMethod); // 生成Java文件 JavaFile javaFile JavaFile.builder(packageName, builder.build()) .build(); // 写入文件到项目中 try { javaFile.writeTo(processingEnv.getFiler()); System.out.println(✅ 生成Builder类成功 packageName . builderClassName); } catch (IOException e) { e.printStackTrace(); } } /** * 指定支持的注解全类名 */ Override public SetString getSupportedAnnotationTypes() { return Set.of(AutoBuilder.class.getCanonicalName()); } }步骤 3SPI 配置让编译器找到处理器这是最关键的一步不配置 SPI编译器无法识别自定义处理器。创建文件路径plaintextprocessor/src/main/resources/META-INF/services/javax.annotation.processing.Processor文件内容处理器全类名plaintextcom.example.processor.AutoBuilderProcessor步骤 4测试使用注解处理器打包annotation和processor模块mvn clean install在业务项目中引入依赖xmldependency groupIdcom.example/groupId artifactIdannotation/artifactId version1.0-SNAPSHOT/version /dependency dependency groupIdcom.example/groupId artifactIdprocessor/artifactId version1.0-SNAPSHOT/version scopeprovided/scope /dependency创建实体类使用AutoBuilderjava运行import com.example.annotation.AutoBuilder; AutoBuilder public class User { private Long id; private String name; }编译项目mvn compile查看生成的代码target/generated-sources/annotations/com/example/UserBuilder.java五、注解处理器常见问题1. 处理器不生效检查 SPI 文件路径、名称、内容是否正确检查getSupportedAnnotationTypes返回的注解全类名清理缓存mvn clean重新编译。2. 编译报错程序包不存在确保注解模块已正确打包依赖引入无误检查模块间依赖关系。3. 可以修改已有源码吗不能直接修改只能生成新的 Java 文件。Lombok 通过修改 AST 实现属于高级黑魔法不推荐新手使用。4. 运行时需要依赖处理器吗不需要处理器是编译期工具打包时scopeprovided/scope即可不会打入运行包。六、注解处理器应用场景代码生成Lombok、MyBatis-Plus 代码生成器、Builder / 工厂模式自动生成编译校验如 NonNull 校验参数非空编译期直接报错框架核心Spring 的 Component 扫描、Dubbo 的 Service 注解文档生成自动生成 API 文档、接口文档。七、总结本文从原理、环境、实战、问题四个维度带你彻底掌握 Java 注解处理器注解处理器是编译期工具无运行时性能损耗核心步骤自定义注解 → 开发处理器 → SPI 配置 → 编译自动生成代码配合 JavaPoet可快速实现代码生成极大提升开发效率是框架开发、代码优化的必备技能。注解处理器是 Java 进阶的核心知识点吃透它你就能读懂大部分框架的底层原理甚至开发自己的代码生成工具
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2431484.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!