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 的配置也加上了
<project>
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<lombok.version>1.18.20</lombok.version>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
官网例子
@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 <T> 类型
* @return 对象
*/
@SuppressWarnings({"unchecked", "rawtypes"})
default <T> T convert(Class<T> clazz) {BeanConvertMapper mapper = BeanConvertMappers.getMapper(this.getClass(), clazz);
return (T) mapper.to(this);
}
}
BeanConvertMapper
定义了一个对象转换的接口
public interface BeanConvertMapper<SOURCE, TARGET> {
/**
* source to target
*
* @param source source
* @return target
*/
TARGET to(SOURCE source);
}
BeanConvertMappers
是一个工具类,提供通过 源对象 Class
和 指标对象 Class
获取 Mapper 办法。
@SuppressWarnings({"rawtypes", "unchecked"})
public class BeanConvertMappers {public static <S, T> BeanConvertMapper<S, T> getMapper(Class<S> sourceClass, Class<T> 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<S, T>) Mappers.getMapper(mapperClass);
}
}
MapperDefinition
保护所有 Mapper
,新增Mapper
只须要注册到 map 即可。
@SuppressWarnings("rawtypes")
public class MapperDefinition {private static Map<String, Class> MAPPERS = new HashMap<>(16);
static {registerMapper(CarDto.class, Car.class, CarDtoToCarMapper.class);
// 新增的 Mapper 在这注册
MAPPERS = MapUtil.unmodifiable(MAPPERS);
}
/* Mapper 定义 */
@Mapper
public interface CarDtoToCarMapper extends BeanConvertMapper<LabelingReq, LabelingBO> { }
/* Mapper 定义 */
public static Map<String, Class> getMappers() {return MAPPERS;}
public static <S, T> String generateKey(Class<S> sourceClass, Class<T> targetClass) {return sourceClass.getName() + targetClass.getName();}
private static <S, T> void registerMapper(Class<S> sourceClass, Class<T> targetClass, Class<? extends BeanConvertMapper<S, T>> 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();}
你有更好的计划吗,一起分享下