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 static  BeanConvertMapper 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 static  String generateKey(Class sourceClass, Class targetClass) {
        return sourceClass.getName() + targetClass.getName();
    }

    private static  void 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();
}

你有更好的方案吗,一起分享下

你可能感兴趣的:(javabean)