• mapStruct笔记


    背景

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

    实现

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

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

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

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

    基本流程是

    graph TD; A[解析注解]-->B[生成 Mapper Model] B-->C[将 Model 按规则写入 Freemarker 模板中, 并生成 Impl 文件] C-->D[生成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 ) {
    
    			//Indicates that the annotation's type isn't on the class path of the compiled
    			//project. Let the compiler deal with that and print an appropriate error.
    			if ( oneAnnotation.getKind() != ElementKind.ANNOTATION_TYPE ) {
    				continue;
    			}
    // 遍历包含 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 );
    			}
    		}
    
    		return ANNOTATIONS_CLAIMED_EXCLUSIVELY;
    	}
    

    解析逻辑

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

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

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

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

    	private final String packageName; // 包的名称
    
    	private final String interfaceName; // 接口名称
    
    	private final String implementationName; // 应用名称
    
    	private final List<BeanMapping> beanMappings; // 一系列的 mapping 信息, 每个 method 对应一个 BeanMapping
    

    每一个 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 {
    
    	public static SourceTargetMapper INSTANCE = Mappers.getMapper( SourceTargetMapper.class );
    
    	@Mappings({
    			@Mapping(source = "qax", target = "baz"),
    			@Mapping(source = "baz", target = "qax")
    	})
    	Target sourceToTarget(Source source);
    
    	Source targetToSource(Target target);
    }
    
    

    映射为 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
  • 相关阅读:
    06. 如何去掉数组中的重复性数据
    01. 作用域和值类型引用类型的传递
    韩昊 20190919-5 代码规范,结对
    韩昊 20190919-6 四则运算试题生成,结对
    韩昊 20190919-2 功能测试
    韩昊20190912-1 每周例行报告
    将py文件打包为exe文件方法
    关于四个定义的理解
    韩昊 20190912-2 命令行
    韩昊 20190905-1 每周例行报告
  • 原文地址:https://www.cnblogs.com/SpeakSoftlyLove/p/9794661.html
Copyright © 2020-2023  润新知