MapStruct在项目中封装使用
MapStruct是一个对象属性复制工具,一般作用于不同的分层模型的对象属性复制。
从网上copy了下别人测试的性能对比
pc配置:i7,16G内存
各种Bean拷贝工具比较
工具 | 十个对象复制1次 | 一万个对象复制1次 | 一百万个对象复制1次 | 一百万个对象复制5次 |
---|---|---|---|---|
mapStruct | 0ms | 3ms | 96ms | 281ms |
hutools的BeanUtil | 23ms | 102ms | 1734ms | 8316ms |
spring的BeanUtils | 2ms | 47ms | 726ms | 3676ms |
apache的BeanUtils | 20ms | 156ms | 10658ms | 52355ms |
apache的PropertyUtils | 5ms | 68ms | 6767ms | 30694ms |
来源:MapStruct使用及性能测试,秒杀BeanUtil基础使用 依赖配置 pom.xml配置以下内容,例子中使用了lombok,所以我把lombok的配置也加上了
1.4.2.Final
1.18.20
org.projectlombok
lombok
${lombok.version}
true
org.mapstruct
mapstruct
${org.mapstruct.version}
org.apache.maven.plugins
maven-compiler-plugin
3.8.1
org.projectlombok
lombok
${lombok.version}
org.mapstruct
mapstruct-processor
${org.mapstruct.version}
官方例子
@Data
public class Car {
private String make;
private int numberOfSeats;
private CarType type;
}@Data
public class CarDto {
private String make;
private int seatCount;
private String type;
}@Mapper
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
// 字段名不同时,可以使用@Mapping配置关系
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
}@Test
public void shouldMapCarToDto() {
//given
Car car = new Car("Morris", 5, CarType.SEDAN);
//when
CarDto carDto = CarMapper.INSTANCE.carToCarDto(car);
//then
assertThat(carDto).isNotNull();
assertThat(carDto.getMake()).isEqualTo( "Morris");
assertThat(carDto.getSeatCount()).isEqualTo(5);
assertThat(carDto.getType()).isEqualTo("SEDAN");
}
封装 从上面的例子中,每次使用都需要调用一次
Mapper.INSTANCE
才能获取到Mapper,这样Mapper就会和业务代码耦合在一起,不利于以后替换其他工具。我们可以把对象属性复制的功能抽象成一个接口Convert
,所有Bean都是Convert
的子类,这样每个Bean都有对象转换的能力。public interface Convert extends Serializable {
/**
* 获取自动转换后的JavaBean对象
*
* @param clazz class类型
* @param 类型
* @return 对象
*/
@SuppressWarnings({"unchecked", "rawtypes"})
default T convert(Class clazz) {
BeanConvertMapper mapper = BeanConvertMappers.getMapper(this.getClass(), clazz);
return (T) mapper.to(this);
}
}
BeanConvertMapper
定义了一个对象转换的接口public interface BeanConvertMapper {/**
* source to target
*
* @param source source
* @return target
*/
TARGET to(SOURCE source);
}
BeanConvertMappers
是一个工具类,提供通过源对象Class
和目标对象Class
获取Mapper方法。@SuppressWarnings({"rawtypes", "unchecked"})
public class BeanConvertMappers {public staticBeanConvertMapper getMapper(Class sourceClass, Class targetClass) {
String key = MapperDefinition.generateKey(sourceClass, targetClass);
Class mapperClass = MapperDefinition.getMappers().get(key);
if (mapperClass == null) {
throw new IllegalArgumentException(StrUtil.format("找不到{}转{}的Mapper", sourceClass.getName(), targetClass.getName()));
}
return (BeanConvertMapper) Mappers.getMapper(mapperClass);
}}
MapperDefinition
维护所有Mapper
,新增Mapper
只需要注册到map即可。@SuppressWarnings("rawtypes")
public class MapperDefinition {private static Map MAPPERS = new HashMap<>(16);
static {
registerMapper(CarDto.class, Car.class, CarDtoToCarMapper.class);
// 新增的Mapper在这注册
MAPPERS = MapUtil.unmodifiable(MAPPERS);
}/* Mapper定义 */@Mapper
public interface CarDtoToCarMapper extends BeanConvertMapper {
}/* Mapper定义 */public static Map getMappers() {
return MAPPERS;
}public staticString generateKey(Class sourceClass, Class targetClass) {
return sourceClass.getName() + targetClass.getName();
}private staticvoid registerMapper(Class sourceClass, Class targetClass, Class extends BeanConvertMapper> mapperClass) {
MAPPERS.put(generateKey(sourceClass, targetClass), mapperClass);
}
}
进一步优化 上面的封装解决了Mapper耦合的问题,但是在定义Mapper的时候,还是存在大量的模板接口,是否有更好的方式解决呢?
我想到的方案是:
和mapstruct原理一样,在mapstruct的注解处理器之前,通过注解来生成
BeanConvertMapper
接口,注解大致如下,同时自动注入到map中。新增一个Mapper只需要定义一个注解即可。@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MapperDefinition {/**
* 源对象的Class
*
* @return Class
*/
Class> source();
/**
* 目标对象的Class
*
* @return Class
*/
Class> target();
}
【MapStruct在项目中封装使用】你有更好的方案吗,一起分享下