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();
}
你有更好的方案吗,一起分享下
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。