Java Annotation Processing 概述

article/2025/10/14 14:01:26

文章目录

  • JAVA ANNOTATION PROCESSING
  • 引言
  • 代码实现
    • annotation-processor
      • 实现一个 `Processor`
        • 创建 `AbstractProcessor` 的子类
    • app
  • 测试
  • Reference

JAVA ANNOTATION PROCESSING

引言

源码级别的注解处理最开始出现于 JAVA 5 中, 它提供了一种在编译期生成额外 “源文件” 的机制. 请注意这个 “源文件” 甚至可以不是 Java 文件, 开发者可以根据源码中的注解利用这一机制, 生成任意文件, 如 描述文件,
资源文件, 元数据文件甚至是文档等等.

本文将利用 Java Annotation Processing 的机制在编译期为目标 Class
生成额外的源文件. 代码仓库地址.

注解处理通过多个阶段实现, 每一个阶段开始, 编译器都从源码中搜寻注解并选择能够处理它们的编译器来处理. JAVA 注解处理的相关特性提供于包 javax.annotation.processing 中,
但其已经有一个部分实现的抽象类 AbstractProcessor, 所以最终我们只需要继承这个抽象类即可.
The-Hierarchy-of-AbstractProcessor.png
为了测试 JAVA ANNOTATION PROCESSING 的能力, 我们需要构建两个模块, 其中 annotation-processor 提供注解的能力: 包含处理器本身以及一个自定义的注解, app 模块用于应用其能力并做测试: 包含被注解标注的类.

代码实现

假设在 app 中有一个简单的 Java 类, 形如:

public class Person {private int age;private String name;// ~ getters and setters
}

我们想通过 JAVA ANNOTATION PROCESSING 为其提供一个构造器, 类似 lombok 的 @Builder 做的工作, 期望能让 Person 可以以构造模式的思想来构造, 形如:

// ~ PersonBuilder
Person person = new PersonBuilder().setAge(25).setName("caplike").build();

马上开始!

annotation-processor

负责注解处理. 在本例的逻辑中, 我们会实现, 扫描标记了 @BuilderProperty 的 set 方法, 并生成对应的构造器. 该模块的依赖如下, 我们将会使用到 Google 的 auto-service 库用于生成处理器的元数据文件.

关于 Auto Service

Java annotation processors and other systems use java.util.ServiceLoader to register implementations of well-known types using META-INF metadata. However, it is easy for a developer to forget to update or correctly specify the service descriptors.
AutoService generates this metadata for the developer, for any class annotated with @AutoService, avoiding typos, providing resistance to errors from refactoring, etc.

推荐阅读: ServiceLoader 的原理与设计思想

<dependencies><dependency><groupId>com.google.auto.service</groupId><artifactId>auto-service</artifactId><version>${auto-service.version}</version><scope>provided</scope></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>${maven-compiler-plugin.version}</version><configuration><source>1.8</source><target>1.8</target></configuration></plugin></plugins>
</build>

思路是: 为 POJO 的 set 方法提供一个注解, 然后 annotation-processor 识别标注了注解的目标类和其方法, 生成 PersonBuilder. 如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface BuilderProperty {
}

RetentionPolicy.SOURCE 表示该注解仅能在源码处理而非运行时可用.

实现一个 Processor

创建 AbstractProcessor 的子类

我们需要在 annotation-processor 模块中继承 AbstractProcessor 类来实现自己的处理类, 命名为 BuilderProcessor.

① 我们需要指定这个自定义处理类 “有能力” 处理的注解和支持的源码等级. 可以通过实现 Processor 接口提供的 getSupportedAnnotationTypesgetSupportedSourceVersion 或者通过注解 @SupportedAnnotationTypes 以及 @SupportedSourceVersion 做到. 并且支持通配符.

@AutoService

该注解是 Google auto-service 库中提供的, 用于生成 SPI 的元信息供 ServiceProvider 识别.

推荐阅读: ServiceLoader 的原理与设计思想

② 随后我们需要迭代所有的目标类的注解类型 (在本案例中, 当前处理器只支持 cn.caplike.demo.repository.annotation.processor.BuilderProperty 这个注解, 所以 annotation set 只会有这唯一一个元素).

③ 利用 RoundEnvironment 获取所有标注了 @BuilderProperty 的 “元素”.

④ 获取标注了 @BuilderProperty 的 set 方法.

⑤ 生成文件.

/*** Description: 注解处理器.<br>* Details: 首先我们需要指定当前 Processor 能够处理的注解, 以及支持的源码级别.* 通过实现 Processor 接口的方法 {@code getSupportedAnnotationTypes} & {@code getSupportedSourceVersion}* 和 {@code @SupportedAnnotationTypes} & {@code @SupportedSourceVersion} 注解的方式都可以做到这一点.<br>* Attention: <b>支持通配符, 如: cn.caplike.demo.repository.annotation.processor.*</b>** @author LiKe* @version 1.0.0* @date 2020-03-30 10:27*/
@Slf4j
@SupportedAnnotationTypes({"cn.caplike.demo.repository.annotation.processor.BuilderProperty"}) // ①
@SupportedSourceVersion(SourceVersion.RELEASE_8) // ①
@AutoService(Processor.class)
public class BuilderProcessor extends AbstractProcessor {/*** Description: 对于每一个标注了目标注解的源文件, 编译器都会调用该方法.** @param annotations 配置为需要被处理的注解* @param roundEnv    {@link RoundEnvironment} 携带了当前和之前处理阶段的环境 (上下文) 信息* @return 应该返回为 {@code true}: 当注解都被处理了 (<strong>这样就不会传递给下一个 Processor</strong>).* @author LiKe* @date 2020-03-30 10:34:33* @see AbstractProcessor#process(Set, RoundEnvironment)*/@Overridepublic boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {annotations.forEach(theAnnotation -> { // ②log.debug("需要被处理的注解: {}", theAnnotation.getSimpleName().toString());// 获取所有被标注了 @BuilderProperty 注解的元素. ③final Set<? extends Element> annotatedElements = roundEnv.getElementsAnnotatedWith(theAnnotation);// ~ 保证标注了 @BuilderProperty 注解的方法是 set 方法 ④final Map<Boolean, List<Element>> annotatedMethods = annotatedElements.stream().collect(Collectors.partitioningBy(element ->// 这里我们使用 Element.asType() 方法以接收 TypeMirror 的实例, 后者让我们即便在源码处理阶段也能够获取到类型.((ExecutableType) element.asType()).getParameterTypes().size() == 1 && element.getSimpleName().toString().startsWith("set")));final List<Element> setters = annotatedMethods.get(true);final List<Element> otherMethods = annotatedMethods.get(false);otherMethods.forEach(element -> processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,"@BuilderProperty must be applied to a setXxx method " + "with a single argument", element));if (!setters.isEmpty()) {final String className = ((TypeElement) setters.get(0).getEnclosingElement()).getQualifiedName().toString();final Map<String, String> setterMap = setters.stream().collect(Collectors.toMap(setter -> setter.getSimpleName().toString(),setter -> ((ExecutableType) setter.asType()).getParameterTypes().get(0).toString()));// ~ 生成文件 ⑤try {this.writeBuilderFile(className, setterMap);} catch (IOException e) {throw new RuntimeException(e);}}});return true;}private void writeBuilderFile(String className, Map<String, String> setterMap) throws IOException {String packageName = null;final int indexOfLastDot = StringUtils.lastIndexOf(className, ".");if (indexOfLastDot > 0) {packageName = className.substring(0, indexOfLastDot);}final String simpleClassName = className.substring(indexOfLastDot + 1);final String builderClassName = className + "Builder";final String builderSimpleClassName = builderClassName.substring(indexOfLastDot + 1);final JavaFileObject builderFile = processingEnv.getFiler().createSourceFile(builderClassName);try (PrintWriter out = new PrintWriter(builderFile.openWriter())) {if (StringUtils.isNotBlank(packageName)) {out.print("package ");out.print(packageName);out.println(";");out.println();}out.print("public class ");out.print(builderSimpleClassName);out.println(" {");out.println();out.print("    private ");out.print(simpleClassName);out.print(" object = new ");out.print(simpleClassName);out.println("();");out.println();out.print("    public ");out.print(simpleClassName);out.println(" build() {");out.println("        return object;");out.println("    }");out.println();setterMap.forEach((methodName, argumentType) -> {out.print("    public ");out.print(builderSimpleClassName);out.print(" ");out.print(methodName);out.print("(");out.print(argumentType);out.println(" value) {");out.print("        object.");out.print(methodName);out.println("(value);");out.println("        return this;");out.println("    }");out.println();});out.println("}");}}
}

app

该模块引入 annotation-processor 并使用它提供的能力, 只需要:

<!--引入 annotation-processor -->
<dependency><groupId>cn.caplike.demo.repository.java.annotation.processor</groupId><artifactId>annotation-processor</artifactId><version>1.0.0-SNAPSHOT</version>
</dependency>

在 app 模块中, Person 类应该形如:

public class Person {private int age;private String name;@BuilderPropertypublic void setAge(int age) {this.age = age;}@BuilderPropertypublic void setName(String name) {this.name = name;}// getters …}

测试

首先我们在 annotation-processor 根目录运行 mvn clean compile 编译该模块,
annotation-processor.png
可以看到引入了该模块的 app 工程的 class 文件夹里已经有了名为 PersonBuilder 的文件. 类容为:

public class PersonBuilder {private Person object = new Person();public Person build() {return object;}public PersonBuilder setName(java.lang.String value) {object.setName(value);return this;}public PersonBuilder setAge(int value) {object.setAge(value);return this;}}

这就是我们利用 JAVA Annotation Processing 生成的额外文件.
app.png
在 app 工程中随便写个 main 方法测试:

@Slf4j
public class App {public static void main(String[] args) {log.info("Person built: {}", new PersonBuilder().setAge(18).build());}
}

可以看到输出:

13:20:32.616 [main] INFO cn.caplike.demo.repository.annotation.processor.app.App - Person built: cn.caplike.demo.repository.annotation.processor.app.Person@36d4b5c

- END -

Reference

  • Java source-level annotation processing

  • Java service provider interface

  • Google auto-service

  • ServiceLoader 的原理与设计思想


http://chatgpt.dhexx.cn/article/QuwHNyxS.shtml

相关文章

annotation matlab,matlab 关于annotation函数的一点用法

这几天做毕设&#xff0c;用到了matlab的一些功能&#xff0c;先吐槽一下matlab的官方api写的是真的不好&#xff0c;很多东西不解释清楚。 首先对于所有的annotation函数&#xff0c;里面不论是维度还是大小参数&#xff0c;都是归一化到[0,1]之间的&#xff0c;也就是说&…

Android AnnotationProcessor

Android AnnotationProcessor 一.项目结构二.定义注解三.实现注解处理器(一)依赖(二)注解处理器(三)处理注解 四.使用注解处理器(一)依赖(二)使用注解(三)生成的代码 五.注意事项 注解处理器通常可以用在模块间解藕、自动生成代码等地方&#xff0c;比如router路由或者butterkn…

Annotation Processor

annotationProcessor和android-apt的功能是一样的&#xff0c;它们是替代关系。annotationProcessor是APT工具中的一种&#xff0c;他是google开发的内置框架&#xff0c;不需要引入&#xff0c;可以直接在build.gradle文件中使用。android-apt是由一位开发者自己开发的apt框架…

annotation是什么,用处,举例

1.概念&#xff1a;注解Annotation是java 1.5的新特性&#xff0c;是一种能够添加到 Java 源代码的语法元数据。类、方法、变量、参数、包都可以被注解&#xff0c;可用来将信息元数据与程序元素进行关联。Annotation 中文常译为“注解”。 2.用处&#xff1a; (1)生成文档。这…

Web大学生网页作业成品:个人博客主页 (纯HTML+CSS代码)

Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 个人博客网站 | 个人主页介绍 | 个人简介 | 个人博客设计制作 | 等网站的设计与制作 | 大学生个人HTML网页设计作品 | HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&…

vue搭建博客

使用vue2.0/3.0搭建博客&#xff0c;前端情绪员 实现原理安装nodejs和vue写项目项目打包和托管项目创建github仓库安装git托管项目到github仓库检查仓库并设置pages购买域名和解析域名修改更新项目ps(gitee搭建vue博客) 实现原理 使用vue打包出来的dist&#xff0c;把dist中的…

博客中动态图的制作

在博客中的动态图片的制作&#xff0c;可以在Android Studio里面连上手机进行视频的录制。之后用在视频的基础上用gifcam软件转化成gif动态图&#xff1a; 下载gitcam绿色版软件进行视频的处理&#xff1a; 打开GIFcam转化成gif格式的动态图&#xff1a;

手把手,从零开始搭建个人博客网站(附源码)

项目介绍 一、项目功能&#xff1a; 实现登录功能 密码数据加密 用户管理功能&#xff08;用户增删改查&#xff09; 文章管理功能&#xff08;文章增删改查&#xff09; 文章首页展示 文章评论功能&#xff08;需登录&#xff09; 首页文章展示、 用户管理、文章管理的…

HTML5+CSS编写个人博客界面

刚入门html和css&#xff0c;花了一天入门基础&#xff0c;第二天花了五六个小时完成老师布置的作业——个人博客界面&#xff0c;初学者可以进行参考编写。 注&#xff1a;里面的链接都不能点击 废话少说&#xff0c;看结果&#xff1a; 参考代码&#xff1a; microbloy.htm…

js个人博客设计大作业

视频演示&#xff1a; 大作业演示 图片看效果&#xff1a; 网站规划设计 1.结构设计 共设计3个HTML界面&#xff1a;一个登陆界面&#xff0c;一个注册界面&#xff0c;以及主页面&#xff1b; 2.内容规划 登陆界面包含logo&#xff0c;输入用户名&#xff0c;输入密码和登…

python数据可视化:使用dash给博客制作一个dashboard

项目部署在&#xff1a;https://ffzs-blog-dashboard.herokuapp.com/ 项目代码在&#xff1a;https://github.com/ffzs/dash_blog_dashboard 1.dashboard 仪表板通常提供与特定目标或业务流程相关的关键绩效指标&#xff08;KPI&#xff09;的概览。另一方面&#xff0c;“仪表…

四、登录注册页功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 首先打开在线编辑器进入我们的项目&#xff1a;https://editor.ivx.cn/ 一、登录页功能实现 上一节中已经完成了登录页的页面制作&#xff0c;那么这一节就开始对应的完成登录页的功能实现。 登录页的功能实现主要是对用户…

七、文章管理页面及功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、文章管理页页面制作 文章管理页的基本结构与首页类似&#xff0c;我们复制一个首页&#xff0c;并且重命名首页的名称为文章管理页&#xff1a; 我们接着删除如下图所框选部分内容&#xff1a; 接着重命名导航为内容…

五、文章详情页制作及跳转功能实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、详情页制作 在之前的章节中&#xff0c;我们已经制作完毕了登录、注册、首页等内容&#xff0c;在这一节中&#xff0c;我们编写详情页以及详情页功能制作。 详情页页面如下&#xff1a; 详情页头部也就是一个头部栏&…

三、登录页制作《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、登录页实现 本节需要做的登录页如下&#xff1a; 该页面我们复习可以的值&#xff0c;首先设置整个页面页面的垂直和水平对其为居中&#xff0c;随后一个容器包裹对应的登录区域&#xff0c;此时我们创建一个页面命名为…

一、首页、详情页、文章编辑页制作《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、首页制作 首页预览如下&#xff1a; 首先在博客页创建一个相对应项目&#xff1a; 接着选择前台&#xff0c;创建一个页面&#xff0c;命名为首页&#xff1a; 接着更改当前屏幕为小屏尺寸&#xff1a; 接着我们分…

二、文章发布页制作及后台实现《iVX低代码/无代码个人博客制作》

注&#xff1a;iVX也有免费直播课《第八期直播课》 一、文章编辑页制作 当首页制作完毕后&#xff0c;需要显示内容就需要有文章数据&#xff0c;此时我们创建一个文章编辑页增加对应的数据。 那么我们创建一个页面&#xff0c;命名为文章发布页&#xff1a; 接着我们查看标…

二、博客首页完成《iVX低代码仿CSDN个人博客制作》

制作iVX 低代码项目需要进入在线IDE&#xff1a;https://editor.ivx.cn/ 一、菜单思路参考及制作 在 CSDN 首页中的菜单部分为一串横排的内容&#xff0c;并且可以进行拖动&#xff1a; 首先咱们添加一个行&#xff0c;命名为菜单&#xff1a; 接着肯定是需要设置上下的内边…

四、博客详情页完成《iVX低代码仿CSDN个人博客制作》

制作iVX 低代码项目需要进入在线IDE&#xff1a;https://editor.ivx.cn/ 一、博客详情页分析 博客详情页大体分为顶部标题、发布时间、作者信息、博文内容&#xff0c;底部的评论我们在此不必做悬浮内容&#xff0c;咱们直接放到博文之下进行显示即可&#xff1b;顶部标题需要…

博客中GIF动画超简单制作

前言: 在写博客的时候图片和视频是非常通俗易懂的,比文字表述更为清晰,有时候演示过程用动画效果更好,毕竟图片只是静态的,看不出整个过程。而录制成视频,体积大,保存发送也比较麻烦,最重要的事博客中不支持这种格式,所以制作成GIF动画,方便、占用空间又小、博客也支…