mapStruct笔记
背景
mapStruct 是一个方便对象转换的工具,类似的工具还有 Dozer, BeanUtils。
实现
mapStruct的核心是在编译期生成基于转换规则的 Impl 文件,运行时直接调用 Impl 文件中的函数。整个 mapStruct 分成三个部分:
自定义注解,指定转换的规则。例如 source, target 等。
freemarker 模板,用来生成 impl 文件。
基于
javax.annotation.processing
的处理模块。
基本流程是
具体解析
具体的解析逻辑是将解析注解内容转化为 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<BeanMapping> 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。