美文网首页
使用SPI简化mapstruct

使用SPI简化mapstruct

作者: 诸葛_小亮 | 来源:发表于2020-11-17 15:34 被阅读0次

    使用SPI简化mapstruct

    参考资料

    重复重复再重复引发的问题

    近期的工作中,使用了 mapstruct 工具类库 ,简化了对象之间的转换工作。在使用过程中,发现存在重复工作量,频繁的创建接口,使用接口,烦不胜烦。
    即使是提供一个基类接口,也避免不了创建接口的工作。一致期待java有类似net中的 AutoMap 工具类,然而并没有暖用 ,于是萌发了自己写一个注解的念头...
    在使用 mapstruct 过程中,了解到其使用了SPI技术,根据 @Mapper 注解生成了接口实现源码,于是就想,能不能自定义注解,使用SPI技术自动生成 mapstruct 接口, mapstruct-processer 在根据生成的接口生成接口实现类?结合 Spring 框架,提供一个转换入口,统一对象转换调用方式?
    于是开始了简化之旅...

    简化前与简化后对比

    • 简化前
    @Data
    pulic class IotSettingDto{
        private String name;
    }
    
    @Data
    pulic class IotSettingMetadata{
        private String name;
    }
    
    
    // 定义接口
    @Mapper(componentModel = "spring", uses = {
            IotDataTypeConverter.class
    })
    public interface IIotSettingConverter extends AbstractObjectConverter<
            IotSettingMetadata,
            IotSettingDto> {
    }
    
    // 使用
    @Service
    @AllArgsConstructor
    public class IotSettingAppService implements IIotSettingAppService {
    
        private final IIotSettingConverter settingConverter;
        // 其他converter
        ...
        
        public IotSettingMetadata test(IotSettingDto dto){
            ...
            return settingConverter.map(dto);
            ...
        }
    }
    
    • 简化后
    @Data
    pulic class IotSettingDto{
        private String name;
    }
    
    @Data
    @AutoMap(
            type = IotSettingMetadata.class,
            uses = IotDataTypeConverter.class
    )
    pulic class IotSettingMetadata{
        private String name;
    }
    
    
    // 使用
    @Service
    @AllArgsConstructor
    public class IotSettingAppService implements IIotSettingAppService {
    
        private final ObjectConverterContext converterContext;
        
        public IotSettingMetadata test(IotSettingDto dto){
            ...
            return converterContext.map(dto, IotSettingMetadata.class);
            ...
        }
    }
    

    前后对比

    1. 简化前需要创建接口
    2. 简化前使用的时候,需要将接口注入到service中
    3. 简化后,使用 @AutoMap 简化接口
    4. 简化后,service只需要依赖 ObjectConverterContext 无需依赖多个 converter 接口

    自定义AutoMap注解,使用SPI生成接口

    定义转换接口基类

    定义基类接口,在生成的代码接口中,默认依赖改接口,统一对象转换入口

    interface AbstractObjectConverter<TSource, TTarget> {
        /**
         * 映射同名属性
         */
        TTarget map(TSource var1);
    
        /**
         * 反向,映射同名属性
         */
        @InheritInverseConfiguration(name = "map")
        TSource reverseMap(TTarget var1);
    
        default List<TTarget> mapList(List<TSource> varList)  {
            return varList.stream().map(c -> map(c)).collect(Collectors.toList())
        }
    
        default List<TSource> reverseMapList(List<TTarget> varList)  {
            return varList.stream().map(c -> reverseMap(c)).collect(Collectors.toList())
        }
    }
    

    自定义automap注解

    • type 表示需要互相转换的类型
    • uses 同 mapstruct
    • otherMaps 主要是提供依赖类的转换内容
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AutoMap {
        Class<?> type();
    
        Class<?>[] uses() default {};
    
        Class[] otherMaps() default {};
    
    }
    

    处理 automap注解

    1. 解析automap注解,得到注解信息
    2. 根据注解信息,使用 JavaFileObject 生成java接口文件
    
    import javax.annotation.processing.AbstractProcessor;
    import javax.annotation.processing.Messager;
    import javax.annotation.processing.RoundEnvironment;
    import javax.annotation.processing.SupportedAnnotationTypes;
    import javax.lang.model.element.AnnotationMirror;
    import javax.lang.model.element.Element;
    import javax.lang.model.element.PackageElement;
    import javax.lang.model.element.TypeElement;
    import javax.lang.model.type.DeclaredType;
    import javax.lang.model.type.MirroredTypeException;
    import javax.lang.model.type.MirroredTypesException;
    import javax.lang.model.type.TypeMirror;
    import javax.tools.Diagnostic;
    import javax.tools.JavaFileObject;
    import java.io.IOException;
    import java.io.PrintWriter;
    import java.io.Writer;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    import java.util.function.Consumer;
    
    
    @SupportedAnnotationTypes("com.xxx.annotation.AutoMap")
    public class AutoMapAnnotationProcessor extends AbstractProcessor {
    
        @Override
        public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
            Messager messager = processingEnv.getMessager();
    
            for (TypeElement typeElement : annotations) {
                for (Element e : roundEnv.getElementsAnnotatedWith(typeElement)) {
                    //打印
    
                    messager.printMessage(Diagnostic.Kind.WARNING, "Printing:" + e.toString());
                    messager.printMessage(Diagnostic.Kind.WARNING, "Printing:" + e.getSimpleName());
                    messager.printMessage(Diagnostic.Kind.WARNING, "Printing:" + e.getEnclosedElements().toString());
    
                    //获取注解
                    AutoMap annotation = e.getAnnotation(AutoMap.class);
    
    
                    AutoMapInfo info = getAutoMapInfo(annotation, typeElement);
    
    
                    //获取元素名并将其首字母大写
                    String name = e.getSimpleName().toString();
                    char c = Character.toUpperCase(name.charAt(0));
                    name = String.valueOf(c + name.substring(1));
                    //包裹注解元素的元素, 也就是其父元素, 比如注解了成员变量或者成员函数, 其上层就是该类
                    Element enclosingElement = e.getEnclosingElement();
                    //获取父元素的全类名,用来生成报名
                    String enclosingQualifiedname;
                    if (enclosingElement instanceof PackageElement) {
                        enclosingQualifiedname = ((PackageElement) enclosingElement).getQualifiedName().toString();
                    } else {
                        enclosingQualifiedname = ((TypeElement) enclosingElement).getQualifiedName().toString();
                    }
    
                    try {
    
    
                        System.out.println("hello,world: packages--" + enclosingQualifiedname);
    
                        //生成包名
                        String generatePackageName = enclosingQualifiedname.substring(0, enclosingQualifiedname.lastIndexOf("."));
                        // 生成的类名
                        String genarateClassName = "I" + name + info.simpleName + "Converter";
                        //创建Java 文件
                        JavaFileObject f = processingEnv.getFiler().createSourceFile(
                                enclosingQualifiedname + "." +
                                        genarateClassName);
                        // 在控制台输出文件路径
                        messager.printMessage(Diagnostic.Kind.WARNING, "Printing: " + f.toUri());
                        Writer w = f.openWriter();
                        try {
                            PrintWriter pw = new PrintWriter(w);
                            pw.println("package " + enclosingQualifiedname + ";");
                            pw.println("import com.masterlink.iot.convert.AbstractObjectConverter;");
                            pw.println("import " + info.qualifiedName + ";");
    
                            String usesStr = "uses = {#USES#}";
                            List<String> usesClassList = new ArrayList<>();
    
                            for (AutoMapInfo mapInfo :
                                    info.uses) {
                                pw.println("import " + mapInfo.qualifiedName + ";");
                                usesClassList.add(mapInfo.simpleName + ".class");
                            }
    
                            for (AutoMapInfo otherMap :
                                    info.otherMaps) {
                                pw.println("import " + otherMap.qualifiedName + ";");
                                usesClassList.add(otherMap.simpleName + ".class");
                            }
    
                            String usesClassStr = String.join(",", usesClassList);
                            usesStr = usesStr.replace("#USES#", usesClassStr);
    
                            pw.println("import org.mapstruct.Mapper;");
    
                            String mapper = "@Mapper(componentModel = \"spring\",#USES#)";
                            mapper = mapper.replace("#USES#", usesStr);
                            pw.println(mapper);
                            String interfaceStr =
                                    "public interface #INTERFACE_NAME# extends AbstractObjectConverter<#SOURCE#,#TARGET#> {}";
                            interfaceStr = interfaceStr.replace("#INTERFACE_NAME#", genarateClassName)
                                    .replace("#SOURCE#", name)
                                    .replace("#TARGET#", info.simpleName);
                            pw.println(interfaceStr);
                            pw.flush();
                        } finally {
                            w.close();
                        }
                    } catch (IOException e1) {
                        processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR,
                                e1.toString());
    
                    } catch (Exception ex) {
                        System.out.println(ex);
                    }
    
    
                }
            }
    
            return true;
        }
        
        
        public static AutoMapInfo getAutoMap(AutoMap annotation, Consumer<AutoMap> consumer) {
            AutoMapInfo info = new AutoMapInfo();
            try {
                consumer.accept(annotation);
            } catch (MirroredTypeException mte) {
                DeclaredType classTypeMirror = (DeclaredType) mte.getTypeMirror();
                TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
                info.simpleName = classTypeElement.getSimpleName().toString();
                info.qualifiedName = classTypeElement.getQualifiedName().toString();
            }
            return info;
        }
    
        private static AutoMapInfo getAutoMapInfo(AutoMap annotation, TypeElement annotationTypeElement) {
    
            List<? extends AnnotationMirror> annotationMirrors = annotationTypeElement.getAnnotationMirrors();
    
            AutoMapInfo info = getAutoMap(annotation, AutoMap::type);
    
            try {
                annotation.uses();
            } catch (MirroredTypeException mte) {
    
            } catch (MirroredTypesException mte) {
                List<? extends TypeMirror> typeMirrors = mte.getTypeMirrors();
                for (TypeMirror tm : typeMirrors
                ) {
    
                    DeclaredType classTypeMirror = (DeclaredType) tm;
                    TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
                    AutoMapInfo useInfo = new AutoMapInfo();
                    useInfo.simpleName = classTypeElement.getSimpleName().toString();
                    useInfo.qualifiedName = classTypeElement.getQualifiedName().toString();
                    info.uses.add(useInfo);
                }
            }
    
    
            try {
                annotation.otherMaps();
            } catch (MirroredTypeException mte) {
    
            } catch (MirroredTypesException mte) {
                List<? extends TypeMirror> typeMirrors = mte.getTypeMirrors();
                for (TypeMirror tm : typeMirrors
                ) {
    
                    DeclaredType classTypeMirror = (DeclaredType) tm;
                    TypeElement classTypeElement = (TypeElement) classTypeMirror.asElement();
    
                    System.out.println("otherMap QualifiedName:" + classTypeElement.getQualifiedName().toString());
                    System.out.println("otherMap SimpleName:" + classTypeElement.getSimpleName().toString());
    
                    AutoMap autoMap = classTypeElement.getAnnotation(AutoMap.class);
                    AutoMapInfo typeInfo = getAutoMap(autoMap, (a) -> a.type());
    
                    System.out.println("source package:" + classTypeElement.getQualifiedName().toString());
                    System.out.println("source:" + classTypeElement.getSimpleName().toString());
                    System.out.println("target:" + typeInfo.simpleName);
                    String conveterName = "I" + classTypeElement.getSimpleName().toString() + typeInfo.simpleName
                            + "Converter";
                    System.out.println("conveterName:" + conveterName);
    
                    String enclosingQualifiedname = classTypeElement.getQualifiedName().toString();
                    String converterPackageName =
                            enclosingQualifiedname.substring(0, enclosingQualifiedname.lastIndexOf("."));
    
                    AutoMapInfo otherInfo = new AutoMapInfo();
                    otherInfo.simpleName = conveterName;
                    otherInfo.qualifiedName = converterPackageName + "." + conveterName;
                    System.out.println("otherInfo.qualifiedName:" + otherInfo.qualifiedName);
                    info.otherMaps.add(otherInfo);
    
    
                }
            }
    
            return info;
        }
    
    
    
        private static class AutoMapInfo {
            public String qualifiedName;
            public String simpleName;
    
            public List<AutoMapInfo> uses = new ArrayList<>();
            public List<AutoMapInfo> otherMaps = new ArrayList<>();
        }
    }
    

    添加javax.annotation.processing.Processor文件

    resources/META-INF.services 中添加 javax.annotation.processing.Processor 文件,并将自动的Processor添加到文件中,文件内容

    com.xxx.processor.AutomapAnnotationProcessor
    

    在目标项目中pom.xml修改build 过程

    <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <annotationProcessorPaths>
                            <path>
                                <groupId>org.projectlombok</groupId>
                                <artifactId>lombok</artifactId>
                            </path>
                            <path>
                                <groupId>com.xxx</groupId>
                                <artifactId>automap-processor</artifactId>
                            </path>
                            <path>
                                <groupId>org.mapstruct</groupId>
                                <artifactId>mapstruct-processor</artifactId>
                            </path>
                        </annotationProcessorPaths>
                    </configuration>
    
                </plugin>
    

    生成结果

    目标结果在target文件加中,包名和dto类一致


    image.pngimage.png
    @Mapper(componentModel = "spring",uses = {IotDataTypeConverter.class})
    public interface IIotSettingDtoIotSettingMetadataConverter 
        extends AbstractObjectConverter<IotSettingDto,IotSettingMetadata> {}
    
    

    观察生成的接口,和手写的自定义接口完全一致,并且 mapstuct 提供的 @mapper 也生成了对应的实现类

    结合Spring Boot 使用AutoMap

    提供对象转换上下文 ObjectConverterContext 提供对象转换统一接口, 其主要思想是根据原对象类型和目标类型,组装 bean 的名称,在从 ApplicationContext 中根据benaName获取到实现的bean, 执行map方法。

    @Component
    @AllArgsConstructor
    public class ObjectConverterContext {
    
        private final ApplicationContext applicationContext;
    
        public <TSource, TTarget> TTarget map(TSource source, Class<?> targetType) {
            Class<?> sourceType = source.getClass();
    
            String beanName = "I" + sourceType.getSimpleName() + targetType.getSimpleName() + "ConverterImpl";
    
            AbstractObjectConverter<TSource, TTarget> bean = (AbstractObjectConverter) applicationContext.getBean(beanName);
            if (bean == null) {
                return null;
            }
            return bean.map(source);
        }
    
        public <TSource, TTarget> TSource reverseMap(TTarget source, Class<?> targetType) {
            Class<?> sourceType = source.getClass();
    
            String beanName = "I" + targetType.getSimpleName() + sourceType.getSimpleName() + "ConverterImpl";
    
            AbstractObjectConverter<TSource, TTarget> bean = (AbstractObjectConverter) applicationContext.getBean(beanName);
            if (bean == null) {
                return null;
            }
            return bean.reverseMap(source);
        }
    
    }
    
    
    
        @Autowired
        ObjectConverterContext converterContext;
    
    
        @Test
        public void should_map() {
            IotSettingDto source = new IotSettingDto();
            source.setName("SETTING2020");
            source.setDefaultValue("2020");
            source.setDescription("");
            IotSettingMetadata result = converterContext.map(source, IotSettingMetadata.class);
    
            assertThat(result).isNotNull();
            assertThat(result.getName()).isEqualTo(source.getName());
            assertThat(result.getDefaultValue()).isEqualTo(source.getDefaultValue());
        }
    
        @Test
        public void should_reverseMap() {
            IotSettingMetadata source = new IotSettingMetadata();
            source.setName("SETTING20201107");
            source.setDefaultValue("1107");
            source.setDescription("");
            IotSettingDto result = converterContext.reverseMap(source, IotSettingDto.class);
    
            assertThat(result).isNotNull();
            assertThat(result.getName()).isEqualTo(source.getName());
            assertThat(result.getDefaultValue()).isEqualTo(source.getDefaultValue());
        }
    
    

    总结

    1. SPI生成注解代码
      • 提供注解
      • 编写自定义Processor
      • resources/META-INF/services 中添加 javax.annotation.processing.Processor 注入自定义Processor
      • 修改 maven构建过程,将自定义processor指定到maven构建中
    1. ApplicationContext 获取bean

    相关文章

      网友评论

          本文标题:使用SPI简化mapstruct

          本文链接:https://www.haomeiwen.com/subject/rngxiktx.html