要想搞懂mapstruct的底层实现原理,需要先知道java编译流程:.java源文件经过编译器编译成为.class文件,虚拟机执行的就是.class文件

一.语法树

抽象语法树(Abstract Syntax Tree,AST)是在编译器的编译过程中生成的一种数据结构,用于表示源代码的语法结构。

在 Java 的编译过程中,生成 AST 的步骤通常包括以下几个阶段:

词法分析(Lexical Analysis):也称为扫描(Scanning),将源代码分解成一个个的词法单元(Token)。词法单元可以是关键字、标识符、运算符、常量等。

语法分析(Syntax Analysis):也称为解析(Parsing),根据词法单元构建语法树。语法分析器使用文法规则(例如上下文无关文法)来检查词法单元是否符合语法规则,并生成抽象语法树。

语义分析(Semantic Analysis):在生成 AST 的同时,进行语义检查。语义分析器会对语法树进行类型检查、作用域分析、常量表达式计算等。它还会将符号链接到声明,以便后续引用。

中间代码生成(Intermediate Code Generation):有些编译器会在 AST 基础上生成一种中间表示形式,例如三地址码或虚拟机指令。这些中间代码通常更容易进行优化和目标代码生成。

目标代码生成(Code Generation):将 AST 或中间代码转换成目标平台的机器代码。这个阶段将 AST 的每个节点映射到目标机器的指令,生成可以直接执行的最终可执行文件。

因此,AST 的生成通常发生在词法分析和语义分析之间。它是编译过程中重要的中间表示形式,用于表示源代码的语法结构,并为后续的优化和代码生成提供基础。

二.lombok原理

lombok与MapStruct原理都是基于JSR-269规范:

初次使用lombok时,都需要在idea安装lombok插件,这让我们怀疑lombok的实现是通过提供自己的编译器实现的,然而实际情况并非如此,在脱离idea使用javac编译时,只要类路径有lombok的jar包,项

目也可以正常编译通过。其原理在于JSR-269规范。

1.JSR-269规范

JSR 269 规范定义了 Java 编译时注解处理器 API(Pluggable Annotation Processing API(插件式注解处理器)),提供了一种标准的方式让开发者在编译时自动
化处理 Java 源代码中的注解
。具体步骤如下:

  1. 自定义注解

  2. 继承AbstractProcessor类,重写process方法实现自己的注解处理逻辑,

  3. 在META-INF/services目录下创建javax.annotation.processing.Processor文件注册自己实现的Annotation Processor(全路径名)

  4. 编译项目并deploy jar包到maven仓库,在其他项目引入该jar包,在接口上加上我们自定义的注解

在javac编译时即可调用到我们的注解处理器Annotation Processor,从而使得我们有机会对java编译过程中生产的抽象语法树进行修改。

2.原理

javac的编译过程,大致可以分为3个过程:

(1)解析与填充符号表过程

(2)插入式注解处理器的注解处理过程

(3)分析与字节码生成过程

解析与填充符号表过程会将源码转换为一棵抽象语法树(Abstract Syntax Tree,AST),AST是一种用来描述程序代码语法结构的树形表示方式,语法树的每一个节点都代表着程序代码中的一个语法结构
(Construct),例如包、类型、修饰符、运算符、接口、返回值甚至代码注释等都可以是一个语法结构。在插入式注解处理器的注解处理过程中,lombok对第一步骤得到的AST进行处理,找到类似@Data注解所在类对应的语法树(AST),然后修改该语法树(AST),增加getter和setter等方法的相应树节点,javac使用修改后的抽象语法树(AST)生成字节码文件,因此最终生成的class文件中包含了这些
自动生成的getter,setter方法。

二.mapstruct原理

MapStruct 是用于 Bean 之间映射的工具库之一,其底层实现就是基于JSR-269。
阅读源码

(1)发现 mapstruct主要有两个jar包

image

(2)通过根据JSR-269可以知道MappingProcessor就是mapstruct的入口

image

(3)com.sun.tools.javac.main.JavaCompiler#compile方法的有段代码在编译的时候会调用到MappingProcessor

说明

com.sun.tools.javac.main.JavaCompiler 是 Java 编译器的主要类,它是 JDK 中 javac 命令行工具的实现。该类负责解析和编译 Java 源代码,并生成字节码文件。
JavaCompiler 类位于 com.sun.tools.javac.main 包中,属于 JDK 内部实现的一部分,并不是公开的 API。它提供了编译 Java 代码的核心功能

image

(4)Mapper注解定义如下

image

(5)MappingProcessor实现如下

image

process方法实现注解处理逻辑做代码改造工作。

image

(6)impl实现类生成

proess方法-->
processMapperElements( deferredMappers, roundContext );
-->processMapperTypeElement( context, mapperElement );
--->model = process( context, processor, mapperTypeElement, model );
--->processor.process( context, mapperTypeElement, sourceElement );
--->MapperRenderingProcessor类里可以看到有个createSourceFile方法(Filer 接口),该方法会创建xxxxImpl类。另外,MapStruct 库的注解处理器生成源代码文件利用了模板引擎 FreeMarker 库

参考链接:
https://juejin.cn/post/6956190395319451679
https://blog.csdn.net/weixin_43983762/article/details/105867398
https://blog.csdn.net/begefefsef/article/details/126434950
https://developer.aliyun.com/article/944036
https://nullwy.me/2017/04/javac-api/

以下是来自chatgpt的回答:

1.MapStruct原理
是一个Java注解处理器,它基于编译时代码生成的原理,用于自动化Java bean类型之间的映射工作。以下是MapStruct的工作原理的详细解读:

注解处理: 在Java编译阶段,MapStruct通过Java的注解处理器API(JSR 269)扫描源代码中的接口或抽象类,这些接口或抽象类使用了MapStruct提供的注解,如@Mapper或@Mapping。

抽象方法解析: MapStruct分析这些接口或抽象类中定义的抽象方法,这些方法通常表示从一个bean类型转换到另一个bean类型的映射。MapStruct解析方法参数和返回值,确定源类型和目标类型。

属性映射分析: MapStruct使用内置的策略或者通过@Mapping注解指定的策略来分析两个bean之间属性的对应关系。如果属性名称相同且类型兼容,MapStruct会自动映射这些属性。

代码生成: 一旦分析完毕,MapStruct会为每个映射接口生成一个实现类。这个实现类包含了具体的映射逻辑,能够将一个对象的属性复制到另一个对象的相应属性中。

编译时优化: 因为所有的映射代码都是在编译时生成的,所以运行时没有反射的开销,这意味着MapStruct生成的代码在性能上与手写的映射代码类似。

类型转换: 如果源属性和目标属性的类型不匹配,MapStruct会尝试使用内置的类型转换器或者开发者可以自定义转换逻辑来进行转换。

嵌套属性和集合: MapStruct也支持嵌套属性的映射和集合类型的映射,允许复杂的数据结构之间的转换。

更新现有对象: 除了创建目标类型的新实例之外,MapStruct还允许将属性映射到现有的实例上。

构建工具集成: MapStruct可以很容易地与Maven或Gradle等构建工具集成,自动在编译过程中执行代码生成。

IDE集成: 由于MapStruct是在编译时生成代码,所以大多数Java IDE都能够很好地支持MapStruct,包括在生成的实现类中进行代码跳转和调试。

综上所述,MapStruct的主要优点是它通过在编译时生成映射代码,从而提供快速、类型安全并且易于维护的映射代码,而不需要开发者手动编写大量的映射逻辑。

2.Java的注解处理器API(JSR 269)说明如下
Java的注解处理器API(JSR 269),正式名称为Pluggable Annotation Processing API,是Java SE 6中引入的一项功能,允许开发者创建自定义注解处理器,这些处理器在编译时可以检查和处理注解。这是MapStruct工作原理的核心部分。以下是注解处理器如何在MapStruct中工作的详细说明:

编译器集成: 注解处理器在Java编译器(javac)的编译过程中运行。当javac编译Java源代码时,它会检查源代码中使用的注解。

发现注解处理器: 开发者可以通过在服务配置文件中声明或者使用编译器参数来指定注解处理器。MapStruct的注解处理器被声明在META-INF/services/javax.annotation.processing.Processor文件中,所以javac能够自动加载并调用它。

处理注解: 一旦javac发现了MapStruct的注解处理器,它会将所有使用了MapStruct注解(如@Mapper)的源代码元素(类、接口、方法等)传递给处理器。MapStruct的处理器会分析这些元素。

抽象语法树(AST): 注解处理器可以访问抽象语法树(AST),这是源代码的一个结构化表示。通过AST,处理器可以读取类、方法、字段和它们的注解等信息。

生成代码: 注解处理器可以生成新的源代码文件。MapStruct的处理器使用这个能力来生成实现了映射接口的具体类。这些生成的类包含了必要的映射逻辑,将一个对象的属性转换到另一个对象。

类型检查: 注解处理器还可以进行类型检查,确认注解的使用是否符合预期的规则。如果不符合,它可以生成编译错误或警告。

环回机制: 如果注解处理器生成新的代码,这些代码可能包含新的注解,javac会在后续的编译轮次中处理这些新生成的代码。这个过程会一直进行,直到没有新的注解处理器输出为止。

多轮处理: 注解处理器可能会在多轮编译过程中运行。在每一轮中,处理器会接收到当前轮次未处理的注解类型。处理器必须管理它在每一轮中的状态,因为它可能需要在多轮中累积信息。

总结来说,MapStruct通过注解处理器在编译时分析使用了MapStruct注解的源代码,并生成相应的映射类。这一切都在Java编译器的控制之下进行,确保了生成的代码是即时和准确的,而且不会对运行时性能产生影响。

3.META-INF文件夹用来做什么的
META-INF目录是Java JAR文件的一部分,它用于存储JAR文件的元数据,也就是关于JAR包本身的数据和配置信息。这个目录通常包含以下几种类型的文件和子目录:

MANIFEST.MF: 这是JAR文件中最重要的文件之一,位于META-INF目录下。它包含了JAR文件的元数据,例如主类(Main-Class)的名称、JAR文件的版本、创建者信息、包的签名信息等。Java运行时使用这些信息来执行和验证JAR文件。

签名文件和签名块文件: 如果JAR文件被签名,那么签名文件(.SF或.DSA或.RSA文件)和签名块文件(.RSA或.DSA或.EC文件)会被放在META-INF目录下。这些文件用来验证JAR文件的完整性和来源。

服务提供者配置文件: 用于服务加载机制的配置文件(如javax.annotation.processing.Processor),它们通常放在META-INF/services目录下,这些文件用来指定服务接口的实现类,以供服务加载器使用。

其他资源文件: 除了上述文件外,META-INF目录还可以包含其他资源文件,如属性文件、配置文件、图片等,这些文件通常由JAR包内的应用程序使用。

索引文件: 有时候,META-INF目录还可能包含用于加速类加载的索引文件(如INDEX.LIST)。

在Java EE或者其他Java技术栈中(如OSGi),META-INF目录用途更广泛,可能包含如persistence.xml、beans.xml、ejb-jar.xml等更多特定于技术的配置文件。

因此,META-INF目录是JAR文件的一个重要组成部分,用于存储JAR包的元数据和配置信息,并且是Java平台中约定俗成用于此目的的目录。