背景:
在开发过程中,为了系统的分层,需要进行VO转换。大家手写各种get、set,代码量太大,而且容易出错。市面上有几种VO转换方式:利用反射、cjlib等等,有的有性能上的问题,有的对特殊的场景处理不是很好。
我们直接在编译阶段直接解析出来get、set,这样既解放了双手,又提高了性能。是不是很香?
MapStruct 应运而生。
MapStruct简介:
是一个Java注释处理器,用于生成类型安全的 bean 映射类。
您所要做的就是定义一个mapper接口,该接口声明任何所需的映射方法。在编译期间,Map struct 将生成此接口的实现。此实现使用普通的Java方法调用来在源对象和目标对象之间进行映射,即没有反射或类似。
与手工编写映射代码相比,MapStruct通过生成繁琐且易于编写的代码来节省时间。遵循约定优于配置方法。
与动态映射框架相比,MapStruct具有以下优势:
- 通过使用普通方法调用而不是反射来快速执行。没有太多的性能损耗。
- 编译时类型安全:只能映射相互映射的对象和属性,不会将实体意外映射到其他的 DTO 等。
- 在构建时清除错误报告,如果映射不完整(并非所有目标属性都已映射);映射不正确(找不到合适的映射方法或类型转换)。构建阶段就能暴露转换错误,不会到运行阶段。
接入方式:
首先需要接入mapstruct,在 pom 中引入jar包。
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.4.2.Final</version>
</dependency>
加入 maven 插件中加入path
注意: maven-compiler-plugin版本一定要3.8.0以上;如果项目有用lombok,path还需要加入lombok-mapstruct-binding
<plugin>
<groupId>org. apache .maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source> <!-- depending on your project -->
<target>1.8</target> <!-- depending on your project -->
< annotation ProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.4.2.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
<!-- other annotation processors -->
</annotationProcessorPaths>
</configuration>
</plugin>
使用就非常简单了,只需要定义一个interface就可以了。
使用案例:
定义两个待转换的类:DemoDo,DemoDto
package com.pt.demo;
import lombok.Data;
import org.bson.types.ObjectId;
@Data
public class DemoDo {
private ObjectId id;
private String name;
private Integer code;
private SubDemo subDemo;
@Data
public static class SubDemo{
private String id;
}
}
package com.pt.demo;
import lombok.Data;
@Data
public class DemoDto {
private String id;
private String name;
private Demo Enum code;
private SubDemo subDemo;
@Data
public static class SubDemo{
private String id;
}
}
package com.pt.demo;
import lombok.AllArgs Constructor ;
@AllArgsConstructor
public enum DemoEnum {
BOY(1,"男"),
GIRL(2,"女");
int code;
String value;
}
最关键的一步来了,我们定义一个interface:DemoMapper
package com.pt.demo;
import org.bson.types.ObjectId;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper(componentModel = "spring")
public interface DemoMapper {
DemoMapper INSTANCE = Mappers.getMapper( DemoMapper.class );
DemoDto convert(DemoDo demoDo);
// 自定义转化,没需求可不加(对象转String)
default String convert(ObjectId id){
if(id == null){
return null;
}
return id.toString();
}
// 自定义转化,没需求可不加(int co de转枚举)
default DemoEnum convert(Integer code){
return code == 1?DemoEnum.BOY:DemoEnum.GIRL;
}
}
解释:
- @Mapper(componentModel = “spring”) 指定是mapstruct的扫描接口,componentModel指定映射模式,默认是default,调用时使用接口上定义的单例( ItemMapper.INSTANCE.convert(itemDO); )也可以指定 spring 托管( @Mapper(componentModel = “spring”) ),调用时可直接使用 @Autowired。
- 对象名不一致,可在方法上加mapping注解( @Mapping(source = “id”,target = “itemId”) )。默认对象名相同时可自动转换。
- 类型不一致时,可在interface中定义default方法,自定义转换。如果是date转String,可以加入@mapping注解添加参数 dateFormat ( @Mapping(source = “localDate”,target = “localDate”,dateFormat = “yyyy-MM-dd”) )
使用maven编译后,会在target里生成相应的代码。
我们测一下试试:
package com.pt.demo;
import org.bson.types.ObjectId;
public class Test {
public static void main(String[] args) {
DemoDo demoDo = new DemoDo(ObjectId.get(),"ge",1,new DemoDo.SubDemo("1"));
System.out.println(demoDo);
DemoDto demoDto = DemoMapper.INSTANCE.convert(demoDo);
System.out.println(demoDto);
}
}
DemoDo(id=619f3aacb9b60d646f271b64, name=ge, code=1, subDemo=DemoDo.SubDemo(id=1))
DemoDto(id=619f3aacb9b60d646f271b64, name=ge, code=BOY, subDemo=DemoDto.SubDemo(id=1))
Process finished with exit code 0
是不是很方便?这下再也不用担心晚上下不了班了。哈哈。适当地利用工具偷个懒,挺好。