乐趣区

关于java: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 的配置也加上了

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

你有更好的计划吗,一起分享下

退出移动版