java mapstruct详解_Mapstruct源码解析- 框架实现原理

只有用过Mapstruct才知道它是有多么的好用与顺手。本篇主要讲述Mapstuct的实现原理,它是怎么去生成转换代码的过程,让大家对这个框架的实现原理有个比较透彻的了解。

1. Java动态编译与JSR 269

首先,我们先重温下java的编译过程:Java源代码-->编译器-->jvm可执行的Java字节码(即虚拟指令)-->jvm-->jvm中解释器-->机器可执行的二进制机器码-->程序。其实java编译器提供了一套完整的api,我们使用接口可以方便地进行动态编译。下面是一个简单的从源代码文件到生成执行文件的完整生成过程。

//创建源文件

String currentDir = System.getProperty("user.dir");

String src = "package com.seewo.phoenix ;"

+ "public class TestCompiler {"

+ " public void disply() {"

+ " System.out.println(\"Hello\");"

+ "}}";

String filename = currentDir + "/src/main/java/com/seewo/phoenix/TestCompiler.java";

File file = new File(filename);

File fileParent = file.getParentFile();

if (!fileParent.exists()) {

fileParent.mkdir();

}

if (!file.exists()) {

file.createNewFile();

}

FileWriter fw = new FileWriter(file);

fw.write(src);

fw.flush();

fw.close();

// 使用JavaCompiler 编译java文件

JavaCompiler jc = ToolProvider.getSystemJavaCompiler();

StandardJavaFileManager fileManager = jc.getStandardFileManager(null, null, null);

Iterable fileObjects = fileManager.getJavaFileObjects(filename);

CompilationTask cTask = jc.getTask(null, fileManager, null, null, null, fileObjects);

cTask.call();

fileManager.close();

// 使用URLClassLoader加载class到内存

URL[] urls = new URL[] { new URL("file:/" + currentDir + "/src/main/java/com/seewo/phoenix/TestCompiler.java") };

URLClassLoader cLoader = new URLClassLoader(urls);

Class c = cLoader.loadClass("com.seewo.phoenix.TestCompiler");

cLoader.close();

// 利用class创建实例,反射执行方法

Object obj = c.newInstance();

Method method = c.getMethod("disply");

method.invoke(obj);

复制代码

大家都会问,这个跟mapstruct说的有半毛钱关系?别急,请看下面的代码过程,在执行JavaCompile#compile中就有去执行processAnnotation(注解扫描与处理)这个步骤。这就是mapstruct注解扫描的入口调用方法。这里对java compile的调用过程就不详细阐述,另外会开一篇讲其中的原理。

f4ecb14957108a3bcc2997671e8991b2.png

跟踪整个调用链路,最后是不是惊喜地发现了MappingProcessor的入口。

0d351cff32b969af188036b7e2afe30b.png

其实,这就是“JSR 269 Pluggable Annotation Processing API”规范,只要程序实现了该API,就能在javac运行的时候得到调用。 举例来说,现在有一个实现了"JSR 269 API"的程序A,那么使用javac编译源码的时候具体流程如下:

javac对源代码进行分析,生成一棵抽象语法树(AST) ;

运行过程中调用实现了"JSR 269 API"的A程序 ;

此时A程序就可以完成它自己的逻辑,包括修改第一步骤得到的抽象语法树(AST) ;

javac使用修改后的抽象语法树(AST)生成字节码文件.

mapstruct本质上就是这样的一个实现了"JSR 269 API"的程序。在使用javac的过程中,它产生作用的具体流程如下:

a54a5b11e073a2366cb7bf422287580d.png

2. 实现原理

2.1 框架主体

在上节中,mapsstruct利用的JSR269规范去扫描和生成的,但是从一个接口定义就能生成一个.class文件,是不是还有点遥远?

f9ba3817b8a0ea0a9ad63392b62e5dc3.png

9b67235d9db18349bd8b970caf9c416f.png

首先我们看下整个框架代码的组成部分,主要分为两个包: org.mapstruct:mapstruct:包含了必要的注解,例如@Mapping; org.mapstruct:mapstruct-processor:包含生成映射器实现的注解处理器。这个就是整个mapstruct框架的入口,继承了注解处理器,在java compile时将会调用process做操作。

//支持@Mapper注解

@SupportedAnnotationTypes("org.mapstruct.Mapper")

public class MappingProcessor extends AbstractProcessor {

//处理入口

@Override

public boolean process(final Set annotations, final RoundEnvironment roundEnvironment) {

if ( !roundEnvironment.processingOver() ) {

RoundContext roundContext = new RoundContext( annotationProcessorContext );

Set deferredMappers = getAndResetDeferredMappers();

processMapperElements( deferredMappers, roundContext );

Set mappers = getMappers( annotations, roundEnvironment );

processMapperElements( mappers, roundContext );

}

return ANNOTATIONS_CLAIMED_EXCLUSIVELY;

}

}

复制代码

Element是一个接口,表示一个程序元素,它可以是包、类、方法或者一个变量。Element已知的子接口有:

PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。

ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。

TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。

VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

2.2 processor处理链

在入口方法中可以看到,主要使用了processor来对解析生成过程进行处理,我们可以看到processor的相关定义。

4dbff3e43ff9b7706623d48b0952530e.png

其中每个process节点都继承ModelElementProcessor基类。

5a883e691e4eed6b4927f818949cd7f0.png

注解处理器以及框架自带的处理类 都以java SPI的方式使用ServiceClas加载进来了,主要实现方法在MappingProcessor#getProcessors中。这里是用serviceClassLoader去加载所有定义好的Process类,形成类似于处理链,类似于责任链的一种方式(用数组记录执行节点而不是用链表)

7979e7193b3954fe194955f4677ad44b.png

主要调用链路图如下,每个process节点都有优先级,顺序执行后将生成的内容写到了文件中。

fc3240bc90cfb2908f4508f14995cc39.png

3.怎样debug注解处理器?

因为这个注解处理器是在解析->编译的过程完成,跟普通的jar包调试不太一样,maven框架为我们提供了调试入口,需要借助maven才能实现debug。所以只需要在编译过程打开debug才可调试。

在项目的pom文件所在目录执行mvnDebug compile

接着用idea打开项目,添加一个remote,端口为8000

打上断点,debug 运行remote即可调试。

44dd39c6b9f70f69c105b54bfca76893.png

4.小结

本篇先简短介绍了java动态编译的过程,并用相关api接口实现了从源文件到执行文件的整个过程,并针对JSR269规范进行了注解扫描与处理过程,结合mapstruct 框架解析了生成转换代码的过程原理。希望大家对mapstruct利用JSR269规范生成代码有所帮助,同样的剖析思路可用于lombok/kotlin等语法糖的原理探究。

参考资料:

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值