背景

mapStruct 是一个方便对象转换的工具,类似的工具还有 Dozer, BeanUtils。

实现

mapStruct的核心是在编译期生成基于转换规则的 Impl 文件,运行时直接调用 Impl 文件中的函数。整个 mapStruct 分成三个部分:

  1. 自定义注解,指定转换的规则。例如 source, target 等。

  2. freemarker 模板,用来生成 impl 文件。

  3. 基于 javax.annotation.processing 的处理模块。

基本流程是

#mermaid-1558415763951 .label{font-family:trebuchet ms,verdana,arial;color:#333}#mermaid-1558415763951 .node circle,#mermaid-1558415763951 .node ellipse,#mermaid-1558415763951 .node polygon,#mermaid-1558415763951 .node rect{fill:#ececff;stroke:#9370db;stroke-width:1px}#mermaid-1558415763951 .node.clickable{cursor:pointer}#mermaid-1558415763951 .arrowheadPath{fill:#333}#mermaid-1558415763951 .edgePath .path{stroke:#333;stroke-width:1.5px}#mermaid-1558415763951 .edgeLabel{background-color:#e8e8e8}#mermaid-1558415763951 .cluster rect{fill:#ffffde!important;stroke:#aa3!important;stroke-width:1px!important}#mermaid-1558415763951 .cluster text{fill:#333}#mermaid-1558415763951 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:trebuchet ms,verdana,arial;font-size:12px;background:#ffffde;border:1px solid #aa3;border-radius:2px;pointer-events:none;z-index:100}#mermaid-1558415763951 .actor{stroke:#ccf;fill:#ececff}#mermaid-1558415763951 text.actor{fill:#000;stroke:none}#mermaid-1558415763951 .actor-line{stroke:grey}#mermaid-1558415763951 .messageLine0{marker-end:"url(#arrowhead)"}#mermaid-1558415763951 .messageLine0,#mermaid-1558415763951 .messageLine1{stroke-width:1.5;stroke-dasharray:"2 2";stroke:#333}#mermaid-1558415763951 #arrowhead{fill:#333}#mermaid-1558415763951 #crosshead path{fill:#333!important;stroke:#333!important}#mermaid-1558415763951 .messageText{fill:#333;stroke:none}#mermaid-1558415763951 .labelBox{stroke:#ccf;fill:#ececff}#mermaid-1558415763951 .labelText,#mermaid-1558415763951 .loopText{fill:#000;stroke:none}#mermaid-1558415763951 .loopLine{stroke-width:2;stroke-dasharray:"2 2";marker-end:"url(#arrowhead)";stroke:#ccf}#mermaid-1558415763951 .note{stroke:#aa3;fill:#fff5ad}#mermaid-1558415763951 .noteText{fill:#000;stroke:none;font-family:trebuchet ms,verdana,arial;font-size:14px}#mermaid-1558415763951 .section{stroke:none;opacity:.2}#mermaid-1558415763951 .section0{fill:rgba(102,102,255,.49)}#mermaid-1558415763951 .section2{fill:#fff400}#mermaid-1558415763951 .section1,#mermaid-1558415763951 .section3{fill:#fff;opacity:.2}#mermaid-1558415763951 .sectionTitle0,#mermaid-1558415763951 .sectionTitle1,#mermaid-1558415763951 .sectionTitle2,#mermaid-1558415763951 .sectionTitle3{fill:#333}#mermaid-1558415763951 .sectionTitle{text-anchor:start;font-size:11px;text-height:14px}#mermaid-1558415763951 .grid .tick{stroke:#d3d3d3;opacity:.3;shape-rendering:crispEdges}#mermaid-1558415763951 .grid path{stroke-width:0}#mermaid-1558415763951 .today{fill:none;stroke:red;stroke-width:2px}#mermaid-1558415763951 .task{stroke-width:2}#mermaid-1558415763951 .taskText{text-anchor:middle;font-size:11px}#mermaid-1558415763951 .taskTextOutsideRight{fill:#000;text-anchor:start;font-size:11px}#mermaid-1558415763951 .taskTextOutsideLeft{fill:#000;text-anchor:end;font-size:11px}#mermaid-1558415763951 .taskText0,#mermaid-1558415763951 .taskText1,#mermaid-1558415763951 .taskText2,#mermaid-1558415763951 .taskText3{fill:#fff}#mermaid-1558415763951 .task0,#mermaid-1558415763951 .task1,#mermaid-1558415763951 .task2,#mermaid-1558415763951 .task3{fill:#8a90dd;stroke:#534fbc}#mermaid-1558415763951 .taskTextOutside0,#mermaid-1558415763951 .taskTextOutside1,#mermaid-1558415763951 .taskTextOutside2,#mermaid-1558415763951 .taskTextOutside3{fill:#000}#mermaid-1558415763951 .active0,#mermaid-1558415763951 .active1,#mermaid-1558415763951 .active2,#mermaid-1558415763951 .active3{fill:#bfc7ff;stroke:#534fbc}#mermaid-1558415763951 .activeText0,#mermaid-1558415763951 .activeText1,#mermaid-1558415763951 .activeText2,#mermaid-1558415763951 .activeText3{fill:#000!important}#mermaid-1558415763951 .done0,#mermaid-1558415763951 .done1,#mermaid-1558415763951 .done2,#mermaid-1558415763951 .done3{stroke:grey;fill:#d3d3d3;stroke-width:2}#mermaid-1558415763951 .doneText0,#mermaid-1558415763951 .doneText1,#mermaid-1558415763951 .doneText2,#mermaid-1558415763951 .doneText3{fill:#000!important}#mermaid-1558415763951 .crit0,#mermaid-1558415763951 .crit1,#mermaid-1558415763951 .crit2,#mermaid-1558415763951 .crit3{stroke:#f88;fill:red;stroke-width:2}#mermaid-1558415763951 .activeCrit0,#mermaid-1558415763951 .activeCrit1,#mermaid-1558415763951 .activeCrit2,#mermaid-1558415763951 .activeCrit3{stroke:#f88;fill:#bfc7ff;stroke-width:2}#mermaid-1558415763951 .doneCrit0,#mermaid-1558415763951 .doneCrit1,#mermaid-1558415763951 .doneCrit2,#mermaid-1558415763951 .doneCrit3{stroke:#f88;fill:#d3d3d3;stroke-width:2;cursor:pointer;shape-rendering:crispEdges}#mermaid-1558415763951 .activeCritText0,#mermaid-1558415763951 .activeCritText1,#mermaid-1558415763951 .activeCritText2,#mermaid-1558415763951 .activeCritText3,#mermaid-1558415763951 .doneCritText0,#mermaid-1558415763951 .doneCritText1,#mermaid-1558415763951 .doneCritText2,#mermaid-1558415763951 .doneCritText3{fill:#000!important}#mermaid-1558415763951 .titleText{text-anchor:middle;font-size:18px;fill:#000}#mermaid-1558415763951 g.classGroup text{fill:#9370db;stroke:none;font-family:trebuchet ms,verdana,arial;font-size:10px}#mermaid-1558415763951 g.classGroup rect{fill:#ececff;stroke:#9370db}#mermaid-1558415763951 g.classGroup line{stroke:#9370db;stroke-width:1}#mermaid-1558415763951 .classLabel .box{stroke:none;stroke-width:0;fill:#ececff;opacity:.5}#mermaid-1558415763951 .classLabel .label{fill:#9370db;font-size:10px}#mermaid-1558415763951 .relation{stroke:#9370db;stroke-width:1;fill:none}#mermaid-1558415763951 #compositionEnd,#mermaid-1558415763951 #compositionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-1558415763951 #aggregationEnd,#mermaid-1558415763951 #aggregationStart{fill:#ececff;stroke:#9370db;stroke-width:1}#mermaid-1558415763951 #dependencyEnd,#mermaid-1558415763951 #dependencyStart,#mermaid-1558415763951 #extensionEnd,#mermaid-1558415763951 #extensionStart{fill:#9370db;stroke:#9370db;stroke-width:1}#mermaid-1558415763951 .branch-label,#mermaid-1558415763951 .commit-id,#mermaid-1558415763951 .commit-msg{fill:#d3d3d3;color:#d3d3d3}#mermaid-1558415763951 {
color: rgb(0, 0, 0);
font: normal normal 400 normal 14px / 25.2px Verdana, Arial, Helvetica, sans-serif;
}
解析注解
生成 Mapper Model
将 Model 按规则写入 Freemarker 模板中, 并生成 Impl 文件
生成Impl对象, 转换时调用

具体解析

具体的解析逻辑是将解析注解内容转化为 Mapper model 对象,然后将 Mapper model 写入 freemarker 模板中。

处理框架

整个注解的解析是通过 java compile[1] 实现的,逻辑包含在MappingProcessor.process 函数中,并通过 MapperGenerationVisitor 进行解析。

	@Override
public boolean process(
final Set<? extends TypeElement> annotations,
final RoundEnvironment roundEnvironment) {
// 遍历需要处理的注解
for ( TypeElement oneAnnotation : annotations ) {
		<span class="hljs-comment">//Indicates that the annotation's type isn't on the class path of the compiled</span>
<span class="hljs-comment">//project. Let the compiler deal with that and print an appropriate error.</span>
<span class="hljs-keyword">if</span> ( oneAnnotation.getKind() != ElementKind.ANNOTATION_TYPE ) {
<span class="hljs-keyword">continue</span>;
}

// 遍历包含 Mapper 注解的 interface and class , 例如 org.mapstruct.ap.test.conversion.SourceTargetMapper

for ( Element oneAnnotatedElement : roundEnvironment.getElementsAnnotatedWith( oneAnnotation ) ) {

// MapperGenerationVisitor 解析每个Mapper 注解的内容 成为一个 Model

oneAnnotatedElement.accept( new MapperGenerationVisitor( processingEnv ), null );

}

}

	<span class="hljs-keyword">return</span> ANNOTATIONS_CLAIMED_EXCLUSIVELY;
}

解析逻辑

MapperGenerationVisitor 负责解析注解为 Mapper model, 并写入 ftl 模板文件中。

MapperGenerationVisitor.retrieveModel 包含了具体的解析逻辑,将注解内容转化为 Mapper Model。

ModelWriter 负责将 Mapper Model 写入 ftl 模板中。

整个逻辑都是围绕 Mapper model 展开的, Mapper 包含如下内容:

	private final String packageName; // 包的名称
<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String interfaceName; <span class="hljs-comment">// 接口名称</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> String implementationName; <span class="hljs-comment">// 应用名称</span>

<span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> List&lt;BeanMapping&gt; beanMappings; <span class="hljs-comment">// 一系列的 mapping 信息, 每个 method 对应一个 BeanMapping</span>

每一个 BeanMapping 对应一个转换函数,它的格式如下:


private final Type sourceType; // 函数的输入参数类型
private final Type targetType; // 函数的结果参数类型
private final List<PropertyMapping> propertyMappings; // 转换函数的每个属性的信息
private final MappingMethod mappingMethod; // 映射的函数
private final MappingMethod reverseMappingMethod; // 翻转映射的函数
private final boolean isIterableMapping; // 是不是迭代

例如 SourceTargetMapper 接口:


@Mapper
public interface SourceTargetMapper {
<span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );

<span class="hljs-meta">@Mappings</span>({
<span class="hljs-meta">@Mapping</span>(source = <span class="hljs-string">"qax"</span>, target = <span class="hljs-string">"baz"</span>),
<span class="hljs-meta">@Mapping</span>(source = <span class="hljs-string">"baz"</span>, target = <span class="hljs-string">"qax"</span>)
})
<span class="hljs-function">Target <span class="hljs-title">sourceToTarget</span><span class="hljs-params">(Source source)</span></span>; <span class="hljs-function">Source <span class="hljs-title">targetToSource</span><span class="hljs-params">(Target target)</span></span>;

}

映射为 Mapper Model 为:

{
"beanMappings":[
{
"iterableMapping":false,
"mappingMethod":{
"name":"sourceToTarget",
"parameterName":"source"
},
"propertyMappings":[
{
"fromConversion":"target.getFoo().intValue()",
"sourceName":"foo",
"sourceType":{
"name":"int",
"primitive":true
},
"targetName":"foo",
"targetType":{
"name":"Long",
"packageName":"java.lang",
"primitive":false
},
"toConversion":"Long.valueOf( source.getFoo() )"
},
Object{...},
Object{...},
Object{...},
Object{...}
],
"reverseMappingMethod":{
"name":"targetToSource",
"parameterName":"target"
},
"sourceType":{
"name":"Source",
"packageName":"org.mapstruct.ap.test.conversion",
"primitive":false
},
"targetType":{
"name":"Target",
"packageName":"org.mapstruct.ap.test.conversion",
"primitive":false
}
}
],
"implementationName":"SourceTargetMapperImpl",
"interfaceName":"SourceTargetMapper",
"packageName":"org.mapstruct.ap.test.conversion"
}

写入模板

写入模板是使用 freemarker 进行编写的,最初写入逻辑很简单,直接使用 ModelWriter 进行写入。ftl 模板的部分内容如下:


package ${packageName}; import java.util.ArrayList;

import java.util.List; public class ${implementationName} implements ${interfaceName} {

上面的 ${packageName}对应的就是 Mapper Model 中的 packageName。

参考

  1. javax.lang.model.element.Element
  2. 编译器 API

原文地址:https://www.cnblogs.com/SpeakSoftlyLove/p/9794661.html

05-28 06:20