MapStruct是一个对象属性复制工具,个别作用于不同的分层模型的对象属性复制。

从网上copy了下他人测试的性能比照

pc配置:i7,16G内存
各种Bean拷贝工具比拟

工具十个对象复制1次一万个对象复制1次一百万个对象复制1次一百万个对象复制5次
mapStruct0ms3ms96ms281ms
hutools的BeanUtil23ms102ms1734ms8316ms
spring的BeanUtils2ms47ms726ms3676ms
apache的BeanUtils20ms156ms10658ms52355ms
apache的PropertyUtils5ms68ms6767ms30694ms
起源: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>

官网例子

@Datapublic class Car {     private String make;    private int numberOfSeats;    private CarType type;}@Datapublic class CarDto {     private String make;    private int seatCount;    private String type; }@Mapperpublic interface CarMapper {     CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);     // 字段名不同时,能够应用@Mapping配置关系    @Mapping(source = "numberOfSeats", target = "seatCount")    CarDto carToCarDto(Car car);}@Testpublic 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();}

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