5

MapStruct is an object attribute copy tool, which generally acts on the object attribute copy of different hierarchical models.

Copy the performance comparison tested by others from the Internet

PC configuration: i7, 16G memory
Comparison of various Bean copy tools

toolTen objects copied 1 time10,000 objects copied 1 time1 million objects copied 1 timeOne million objects copied 5 times
mapStruct0ms3ms96ms281ms
BeanUtil of hutools23ms102ms1734ms8316ms
spring BeanUtils2ms47ms726ms3676ms
BeanUtils of apache20ms156ms10658ms52355ms
apache's PropertyUtils5ms68ms6767ms30694ms
Source: MapStruct usage and performance test, spike BeanUtil

Basic use

Dependent configuration

Pom.xml configures the following content. In the example, lombok is used, so I also added the configuration of 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>

Official example

@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");
}

Encapsulation

From the above example, you need to call Mapper.INSTANCE once for each use to get the Mapper, so the Mapper will be coupled with the business code, which is not conducive to replacing other tools in the future. We can abstract the function of object attribute replication into an interface Convert , all Beans are Convert , so that each Bean has the ability to transform objects.

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 defines an interface for object conversion

public interface BeanConvertMapper<SOURCE, TARGET> {

    /**
     * source to target
     *
     * @param source source
     * @return target
     */
    TARGET to(SOURCE source);

}

BeanConvertMappers is a tool class that provides methods for obtaining Mapper source object Class and target object Class.

@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 maintains all Mapper , adding Mapper only needs to register to the 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);
    }
}

further optimization

The above encapsulation solves the problem of Mapper coupling, but when defining Mapper, there are still a large number of template interfaces. Is there a better way to solve it?

The solution I thought of was:

Similar to the principle of mapstruct, before the annotation processor of BeanConvertMapper , the 06128a29858da8 interface is generated through annotations. The annotations are roughly as follows, and they are automatically injected into the map at the same time. Adding a Mapper only needs to define an annotation.

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface MapperDefinition {

    /**
     * 源对象的Class
     * 
     * @return Class
     */
    Class<?> source();

    /**
     * 目标对象的Class
     * 
     * @return Class
     */
    Class<?> target();
}

Do you have a better plan, share it together


DH镔
232 声望13 粉丝