使用SPI简化mapstruct
参考资料
- https://blog.csdn.net/alex_xfboy/article/details/88253799
- https://blog.csdn.net/d395253902/article/details/103883963
重复重复再重复引发的问题
近期的工作中,使用了 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);
...
}
}
前后对比
- 简化前需要创建接口
- 简化前使用的时候,需要将接口注入到service中
- 简化后,使用
@AutoMap
简化接口 - 简化后,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注解
- 解析automap注解,得到注解信息
- 根据注解信息,使用
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类一致

@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());
}
总结
- SPI生成注解代码
- 提供注解
- 编写自定义Processor
- 在
resources/META-INF/services
中添加javax.annotation.processing.Processor
注入自定义Processor - 修改 maven构建过程,将自定义processor指定到maven构建中
-
ApplicationContext
获取bean
网友评论