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

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

这个站点使用 Akismet 来减少垃圾评论。了解你的评论数据如何被处理