Java注解处理器APT

注解处理器介绍

什么是APT?

在JDK6的时候引入了JSR269的标准,即APT(Annotation Processing Tool),用于在编译时处理源代码中的注解,从而生成额外的代码、配置文件或其他资源。与传统的运行时反射相比,APT在编译时进行处理,可以提高性能并在编译阶段捕获一些问题,减少运行时错误。

APT的工作原理

Java 编译器的工作流程

在介绍注解处理器工作原理之前,我们先来了解一下 Java 编译器的工作流程。

如上图所示,Java 源代码的编译过程可分为三个步骤:

  1. 将源文件解析为抽象语法树;
  2. 调用已注册的注解处理器;
  3. 生成字节码。

如果在第 2 步调用注解处理器过程中生成了新的源文件,那么编译器将重复第 1、2 步,解析并且处理新生成的源文件。每次重复我们称之为一轮(Round)。也就是说,第一轮解析、处理的是输入至编译器中的已有源文件。如果注解处理器生成了新的源文件,则开始第二轮、第三轮,解析并且处理这些新生成的源文件。当注解处理器不再生成新的源文件,编译进入最后一轮,并最终进入生成字节码的第 3 步。

APT的工作流程包括以下阶段:

  • 扫描阶段:编译器会扫描源代码,找出带有特定注解的元素。
    • 编译器检查源码中所有的注解元素,如果没有则整个流程直接结束,否则继续
    • 编译器检查开发者注册了哪些AnnotationProcessor,如果没有则整个流程直接结束,否则继续
    • 编译器拿着所有收集到的注解元素去问Processor们进行认领
    • 当所有注解类型被认领完毕,此阶段结束,进入下一阶段
    • 若仍有注解类型没有被认领,但已经没有多余的处理器了,同样此阶段结束,进入下一阶段
  • 处理阶段:注解处理器将被触发,对扫描到的元素进行处理,并生成新的源代码或资源文件。
    • 编译器将从源码中收集到的注解元素作为输入开启一轮处理
    • 所有开发者注册的注解处理器将排好队串行处理编译器传入的注解元素,在这里需要注意的是注解处理器之间并没有明确的排序规则,可以认为是乱序的,而且每一次可能不一样,不可依赖
    • 若某个注解处理器在处理过程中生产出了新的源码文件,那么此轮处理会立即结束。新生成的源码文件及目前还没处理完的源码元素加在一起作为下一轮的输入(若新生成的源码中没有注解元素,其实是没有意义的)
    • 新一轮处理中,所有的注解处理器依然会被触发,所以需要开发者做好识别,不要产生重复生成新文件的BUG
    • 直到所有注解处理器串行处理后不再产生新文件,处理阶段结束
  • 生成阶段:生成的代码或资源会被编译器包含在编译结果中,最终生成可执行的应用程序。

APT的用途和优势

APT可以应用于许多场景,包括:

  • 自动生成代码:通过自定义注解处理器,可以根据注解自动生成代码,减少重复工作。比如lombokMapStruct
  • 静态检查和约束:利用APT进行静态检查,强制执行编码规范,提高代码质量。
  • 生成配置文件:生成配置文件或资源,提供更灵活的配置方式。

优势包括:

  • 提高性能:在编译时处理,减少了运行时开销。
  • 增强编译时类型检查:通过生成额外代码,可以在编译阶段捕获一些潜在问题。
  • 自动化任务:可以根据需要自动执行一些任务,如代码生成、配置文件生成等。

如何使用APT

要使用APT,需要在项目中配置注解处理器,通常是通过Maven或Gradle来实现。在编译时,注解处理器会自动触发,对带有指定注解的元素进行处理。下面是一个简单的使用APT的示例:

假设我们要实现自动序列化功能,可以使用注解@Serializable标记需要序列化的类,然后通过APT生成相应的序列化和反序列化代码。

首先,定义注解和注解处理器:Serializable,通过APT可以生成与之相关的代码。

@Retention(RetentionPolicy.SOURCE)  
@Target(ElementType.TYPE)  
public @interface Serializable {  }

编写自定义的注解处理器需要实现javax.annotation.processing.AbstractProcessor类,并重写process方法。在process方法中,可以获取被注解标记的元素,并进行相应的处理。

package com.demo.bytecode.apt;  import javax.annotation.processing.AbstractProcessor;  
import javax.annotation.processing.RoundEnvironment;  
import javax.annotation.processing.SupportedAnnotationTypes;  
import javax.annotation.processing.SupportedSourceVersion;  
import javax.lang.model.SourceVersion;  
import javax.lang.model.element.Element;  
import javax.lang.model.element.ElementKind;  
import javax.lang.model.element.TypeElement;  
import javax.tools.JavaFileObject;  
import java.io.IOException;  
import java.io.Writer;  
import java.util.Set;  @SupportedAnnotationTypes("com.demo.bytecode.apt.Serializable")  
@SupportedSourceVersion(SourceVersion.RELEASE_8)  
public class SerializableProcessor extends AbstractProcessor {  @Override  public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {  for (TypeElement annotation : annotations) {  for (Element element : roundEnv.getElementsAnnotatedWith(annotation)) {  if (element.getKind() == ElementKind.CLASS) {  String className = element.getSimpleName().toString();  String packageName = processingEnv.getElementUtils().getPackageOf(element).toString();  generateSerializerClass(packageName, className);  generateDeserializerClass(packageName, className);  }  }  }  return true;  }  private void generateSerializerClass(String packageName, String className) {  String serializerClassName = className + "Serializer";  StringBuilder serializerClassCode = new StringBuilder();  serializerClassCode.append("package ").append(packageName).append(";\n\n");  serializerClassCode.append("import java.io.Serializable;\n");  serializerClassCode.append("import java.io.ObjectOutputStream;\n");  serializerClassCode.append("import java.io.IOException;\n\n");  serializerClassCode.append("public class ").append(serializerClassName)  .append(" implements Serializable {\n\n");  serializerClassCode.append(" private static final long serialVersionUID = 1L;\n\n");  serializerClassCode.append(" public static void serialize(").append(className)  .append(" obj, ObjectOutputStream out) throws IOException {\n");  serializerClassCode.append(" out.writeObject(obj);\n");  serializerClassCode.append(" }\n");  serializerClassCode.append("}\n");  try {  JavaFileObject serializerFile = processingEnv.getFiler().createSourceFile(packageName + "." + serializerClassName);  try (Writer writer = serializerFile.openWriter()) {  writer.write(serializerClassCode.toString());  }  } catch (IOException e) {  e.printStackTrace();  }  }  private void generateDeserializerClass(String packageName, String className) {  String deserializerClassName = className + "Deserializer";  StringBuilder deserializerClassCode = new StringBuilder();  deserializerClassCode.append("package ").append(packageName).append(";\n\n");  deserializerClassCode.append("import java.io.Serializable;\n");  deserializerClassCode.append("import java.io.ObjectInputStream;\n");  deserializerClassCode.append("import java.io.IOException;\n\n");  deserializerClassCode.append("public class ").append(deserializerClassName)  .append(" implements Serializable {\n\n");  deserializerClassCode.append(" private static final long serialVersionUID = 1L;\n\n");  deserializerClassCode.append(" public static ").append(className)  .append(" deserialize(ObjectInputStream in) throws IOException, ClassNotFoundException {\n");  deserializerClassCode.append(" return (").append(className).append(") in.readObject();\n");  deserializerClassCode.append(" }\n");  deserializerClassCode.append("}\n");  try {  JavaFileObject deserializerFile = processingEnv.getFiler().createSourceFile(packageName + "." + deserializerClassName);  try (Writer writer = deserializerFile.openWriter()) {  writer.write(deserializerClassCode.toString());  }  } catch (IOException e) {  e.printStackTrace();  }  }  
}
  • resources 下新建一个 META-INF/services 的目录;
  • services 下新建一个 javax.annotation.processing.Processor 的文件,并将要注册的 Annotation Processor 的全路径写入。

  • javax.annotation.processing.Processor内容如下:
    com.demo.bytecode.apt.SerializableProcessor
    

    上述配置后Maven编译会报如下错误

    服务配置文件不正确, 或构造处理程序对象javax.annotation.processing.Processor: Provider com.demo.bytecode.apt.SerializableProcessor not found时抛出异常错误
    

 通过Maven的编译插件的配置指定如下:

<build>  <plugins>  <plugin>  <groupId>org.apache.maven.plugins</groupId>  <artifactId>maven-compiler-plugin</artifactId>  <version>3.8.1</version>  <configuration>  <compilerArgument>-proc:none</compilerArgument>  </configuration>  </plugin>  </plugins>  
</build>

将上述apt代码打包,这样在我们项目中就可以使用了。 在项目中引入依赖

<dependency>  <groupId>com.demo</groupId>  <artifactId>bytecode-apt</artifactId>  <version>1.0-SNAPSHOT</version>  
</dependency>

项目编译引入jar

<build>  <plugins>  <plugin>  <groupId>org.apache.maven.plugins</groupId>  <artifactId>maven-compiler-plugin</artifactId>  <version>3.8.1</version>  <configuration>  <source>1.8</source>  <target>1.8</target>  <encoding>UTF-8</encoding>  <compilerArguments>  <verbose></verbose>  <bootclasspath>${java.home}/lib/rt.jar:${java.home}/lib/jce.jar:${java.home}/lib/jsse.jar  </bootclasspath>  </compilerArguments>  <annotationProcessorPaths>  <path>  <groupId>com.demo</groupId>  <artifactId>bytecode-apt</artifactId>  <version>1.0-SNAPSHOT</version>  </path>  </annotationProcessorPaths>  </configuration>  </plugin>  </plugins>  
</build>

定义一个方法

@Serializable  
public class Product {  private Long id;  private String name;  private double price;// get set 略
}
Maven编译后在目录下生成了序列化及反序列化类

 

以上示例中的代码仅为示范,实际项目中可能需要更多的处理和逻辑。这些示例演示了如何使用APT来生成代码,以及如何编写自定义的注解处理器来自动化生成和处理代码。

总结

通过本文的详细介绍,读者对APT的概念、原理和应用应该有了更深入的理解。APT作为一个强大的编译时工具,可以帮助开发者实现自动化、提高代码质量和性能,并在项目开发中发挥重要作用。随着技术的不断演进,APT有望在Java开发中扮演更加重要的角色。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/165478.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深入探索Sharding JDBC:分库分表的利器

随着互联网应用的不断发展和用户量的不断增加&#xff0c;传统的数据库在应对高并发和大数据量的场景下面临着巨大的挑战。为了解决这一问题&#xff0c;分库分表成为了一个非常流行的方案。分库分表主流的技术包括MyCat和Sharding JDBC。我们来通过一张图来了解这两者有什么区…

C语言——二周目——程序的翻译与执行环境

一、程序环境 对于一个C语言程序的实现&#xff0c;整个过程一般存在两个不同的环境&#xff0c;分别是翻译环境与执行环境。在翻译环境中&#xff0c;我们所写的源代码经过一系列处理被转换成为可执行的机器指令&#xff1b;在执行环境中&#xff0c;会实际执行代码。 整个程序…

Open3D(C++) 最小二乘拟合平面(拉格朗日乘子法)

目录 一、算法原理二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。 一、算法原理 设拟合出的平面方程为: a x + b y + c

手写redux的connect方法, 使用了subscribe获取最新数据

一. 公共方法文件 1. connect文件 import React, { useState } from "react"; import MyContext from "./MyContext"; import _ from "lodash";// 模拟react-redux的 connect高阶函数 const connect (mapStateToProps, mapDispatchToProps) &…

数学建模——最优连接(基于最小支撑树)

一、概念 1、图的生成树 由图G(V,E)的生成子图G1(V,E1)(E1是E的子集&#xff09;是一棵树&#xff0c;则称该树为图G的生成树&#xff08;支撑树&#xff09;&#xff0c;简称G的树。图G有支撑树的充分必要条件为图G连通。 2、最小生成树问题 连通图G(V,E)&#xff0c;每条边…

STM32串口

前言 提示&#xff1a;这里可以添加本文要记录的大概内容&#xff1a; 目前已经学习了GPIO的输入输出&#xff0c;但是没有完整的显示信息&#xff0c;最便宜的显示就是串口。 000 -111 AVR单片机 已经学会过了&#xff0c; 提示&#xff1a;以下是本篇文章正文内容&#x…

Servlet的生命周期

2023.10.18 WEB容器创建的Servlet对象&#xff0c;这些Servlet对象都会被放到一个集合当中&#xff08;HashMap&#xff09;&#xff0c;这个集合当中存储了Servlet对象和请求路径之间的关系 。只有放到这个HashMap集合中的Servlet才能够被WEB容器管理&#xff0c;自己new的Ser…

《视觉 SLAM 十四讲》V2 第 10 讲 后端优化2 简化BA 【位姿图】

文章目录 第10讲 后端210.1 滑动窗口滤波 和 优化10.1.2 滑动窗口法 10.2 位姿图10.3 实践&#xff1a; 位姿图优化本讲 CMakeLists.txt 10.3.1 g2o 原生位姿图 【Code】10.3.2 李代数上的位姿优化 【Code】 习题10题1 【没推完】 LaTex 第10讲 后端2 滑动窗口优化 位姿图优化…

最新!两步 永久禁止谷歌浏览器 Google Chrome 自动更新

先放效果图&#xff1a; CSDN这个问题最火的大哥的用了没用 像他这样连浏览器都打不开 为什么要禁止chrome自动更新 看到很多搞笑的大哥&#xff0c;说为啥要禁止&#xff1b; 我觉得最大的原因就是chromedriver跟不上chrome的自动更新&#xff0c;导致我们做selenium爬虫的…

CentOS 7 安装 MySQL 8

一、卸载MariaDB MariaDB是MySQL 的一个分支&#xff0c;完全兼容MySQL&#xff0c;包括API和命令行&#xff0c;使之能轻松成为MySQL的代替品。 1、查看版本&#xff08;如果有就卸载&#xff09; rpm -qa|grep mariadbrpm -e --nodeps 文件名 二、下载mysql 1、进入 /usr/…

中枢听觉处理障碍的行为干预方法

作者&#xff1a;听觉健康 在数十年前&#xff0c;中枢听觉处理障碍(CAPD)的研究已经引起了多学科的关注。1937年&#xff0c;Samuel Orton提出某些儿童的学习障碍与不能有效利用听觉有关。Myklebust是提出“中枢性听力障碍”引起儿童语言学习障碍的先驱者之一。二十世纪五十年…

C# Winform编程(7)文件处理技术

文件处理技术 System.IO命名空间System.IO命名空间常用的类System.IO命名空间常用的枚举 File类的常用方法FileInfo类的常用方法File类和FileInfo类的区别文件夹类Directory的常用方法 System.IO命名空间 System.IO命名空间常用的类 类说明File提供用于创建&#xff0c;复制&…

【数之道 08】走进“卷积神经网络“,了解图像识别背后的原理

卷积神经网络 CNN模型的架构Cnn 的流程第一步 提取图片特征提取特征的计算规则 第二步 最大池化第三步 扁平化处理第四步 数据条录入全连接隐藏层 b站视频 CNN模型的架构 图片由像素点组成&#xff0c;最终成像效果由背后像素的颜色数值所决定的 有这样的一个66的区域&#x…

YOLOv5改进实战 | 更换主干网络Backbone(三)之轻量化模型Shufflenetv2

前言 轻量化网络设计是一种针对移动设备等资源受限环境的深度学习模型设计方法。下面是一些常见的轻量化网络设计方法: 网络剪枝:移除神经网络中冗余的连接和参数,以达到模型压缩和加速的目的。分组卷积:将卷积操作分解为若干个较小的卷积操作,并将它们分别作用于输入的不…

2023年中国纸箱机械优点、市场规模及发展前景分析[图]

纸箱机械行业是指涉及纸箱生产和加工的机械设备制造、销售和相关服务的产业。这个行业的主要任务是设计、制造和提供用于生产各种类型和规格纸箱的机械设备&#xff0c;以满足包装行业对纸箱的不同需求。 纸箱机械行业优点 资料来源&#xff1a;共研产业咨询&#xff08;共研网…

SpringBoot + MyBatis 在 jar 中可以启动但是 Idea中无法启动的原因

现象 在idea中启动始终卡住&#xff0c;查看线程堆栈发现一直在mybatis的处理过程中&#xff0c;细究了一下堆栈发现mybatis有使用远程方式加载类的情况&#xff0c;并且此时cpu会飙升&#xff0c; 在命令行中使用java -jar 的形式可以正常启动&#xff0c;但是在idea中启动始…

在 Visual Studio Code (VS Code) 中设置

在 Visual Studio Code (VS Code) 中设置代理服务器的详细教程如下&#xff1a; 打开 Visual Studio Code。 在顶部菜单栏中&#xff0c;点击 "File"&#xff08;文件&#xff09; > "Preferences"&#xff08;首选项&#xff09; > "Settings…

解剖—单链表相关OJ练习题

目录 一、移除链表元素 二、找出链表的中间节点 三、合并两个有序链表 四、反转链表 五、求链表中倒数第k个结点 六、链表分割 七、链表的回文结构 八、判断链表是否相交 九、判断链表中是否有环(一) 十、 判断链表中是否有环(二) 注&#xff1a;第六题和第七题牛…

win11快速打开蓝牙设置的方法

win11快速打开蓝牙设置的方法 Windows 11 中快速连接蓝牙设备的 3 种方法&#xff01;_哔哩哔哩_bilibili 如何为Windows设置快捷键&#xff1f;_百度知道 (baidu.com) Win11怎么隐藏文件夹?Win11通过命令隐藏文件夹的方法_windows11_Windows系列_操作系统_脚本之家 (jb51.net…

《动手学深度学习 Pytorch版》 9.5 机器翻译与数据集

机器翻译&#xff08;machine translation&#xff09;指的是将序列从一种语言自动翻译成另一种语言&#xff0c;基于神经网络的方法通常被称为神经机器翻译&#xff08;neural machine translation&#xff09;。 import os import torch from d2l import torch as d2l9.5.1 …