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 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在项目中封装使用】你有更好的方案吗,一起分享下

    推荐阅读