mapStruct筆記


背景

mapStruct 是一個方便對象轉換的工具,類似的工具還有 Dozer, BeanUtils。

實現

mapStruct的核心是在編譯期生成基於轉換規則的 Impl 文件,運行時直接調用 Impl 文件中的函數。整個 mapStruct 分成三個部分:

  1. 自定義注解,指定轉換的規則。例如 source, target 等。

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

  3. 基於 javax.annotation.processing 的處理模塊。

基本流程是

解析注解
生成 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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM