效率提升工具-对象COPY之MapStruct

167 阅读4分钟

MapStruct 是一个基于 Java 的代码生成库,用于在不同的 Java bean 类型之间进行映射。它消除了为对象映射编写样板代码的需求,使开发人员能够专注于应用程序的业务逻辑。

介绍

MapStruct是基于JSR 269的Java注释处理器,用于生成类型安全的 Bean 映射类。

通过 MapStruct,您可以使用称为映射器接口的接口定义源对象和目标对象之间的映射关系。然后,该库会在编译时根据定义的映射关系为这些接口生成实现代码。这种方法确保了类型安全的映射,并通过在运行时消除反射来提高性能。

MapStruct 支持不同结构的对象之间的映射,包括嵌套对象、集合和数组。它还提供了高级功能,如属性映射、映射条件和通过注解和高级配置选项进行映射自定义。

安装使用

maven工程,参考:mapstruct.org/documentati…

...
<properties>
    <org.mapstruct.version>1.5.5.Final</org.mapstruct.version>
</properties>
...
<dependencies>
    <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>
                <source>1.8</source> <!-- depending on your project -->
                <target>1.8</target> <!-- depending on your project -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>org.mapstruct</groupId>
                        <artifactId>mapstruct-processor</artifactId>
                        <version>${org.mapstruct.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

mapstruct和lombok混用参考这个git:github.com/mapstruct/m…

性能对比

工具实现方式缺点说明
mapstructgetter/setter方法需要了解注解和配置项语法JSR269注解处理器在编译期自动生成Java Bean转换代码,支持可配置化,扩展性强
orika动态生成字节码首次调用耗时较久,性能适中采用javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件
Spring BeanUtils反射机制不支持名称相同但类型不同的属性转换
Apache BeanUtils反射机制需要处理编译期异常,性能最差
dozer反射机制性能差使用reflect包下Field类的set(Object obj, Object value)方法进行属性赋值
BeanCopier反射机制BeanCopier只拷贝名称和类型都相同的属性。即便基本类型与其对应的包装类型也不能相互转换;使用ASM的MethodVisitor直接编写各属性的get/set方法

就性能而言:mapstruct性能无疑是是最高的,接下来依次是Spring BeanUtils>orika>BeanCopier>dozer>apache BeanUtils

使用案例

下面的代码演示了如何使用Map Struct实现Java  Bean之间的映射。假设我们有一个表示汽车的类Car,并且还有一个数据传输对象(DTO)CarDTO。

这两个类非常相似,只是表示作为数量的属性名称是不同的并且,在Car对象中,表示汽车类型的字段是一个枚举,而在CarDTO中,直接使用字符串表示。

Car.java

public class Car {
    private String make;
    private int numberOfSeats;
    private CarType type;
    //constructor, getters, setters etc.
    }
    
  static enum CarType {
    SEDAN
}

CarDTO.java

public class CarDto {
 
    private String make;
    private int seatCount;
    private String type;
 
    //constructor, getters, setters etc.
}

2.1 Mapper接口

要生成一个CarDTO与Car对象相互转换的映射器,我们需要定义一个mapper接口。

CarMapper.java

@Mapper
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); 3
 
    @Mapping(source = "numberOfSeats", target = "seatCount")
    CarDto carToCarDto(Car car); 2
}

1、@Mapper注解标记这个接口作为一个映射接口,并且是编译时MapStruct处理器的入口。

2、真正实现映射的方法需要源对象作为参数,并返回目标对象。映射方法的名字是随意的。对于在源对象和目标对象中,属性名字不同的情况,可以通过@Mapping注解来配置这些名字。我们也可以将源类型与目标类型中类型不同的参数进行转换,在这里就是通过type属性将枚举类型转换为一个字符串。当然在一个接口里可以定义多个映射方法。MapStruct都会为其生成一个实现。

3、自动生成的接口的实现可以通过Mapper的class对象获取。按照惯例,接口中会声明一个成员变量INSTANCE,从而让客户端可以访问Mapper接口的实现。

2.2 编译

因为MapStruct是以Java编译器插件的形式来处理注解,生成mapper接口的实现。因此在使用之前我们必须手工的编译(IDE的自动编译功能不会使用到MapStruct这个插件功能)。

执行maven命令:

mvn compile

可以看到在target目录来多个一个类CarMapperImpl.class,如下图所示:

QQ截图20160610205607.png

这个类实际上就是map struct插件自动帮助我们根据CarMapper接口生成的实现类。我们可以通过IDE的反编译功能查看自动生成的实现类的源码,如下图所示:

QQ截图20160610205831.png

通过反编译的源码,我们可以看出,对于属性名称不同的情况(seatCount与numberOfSeats)、以及属性类型不同(枚举类型的type与字符串类型的type)都自动帮助我们转换了。对于属性名称不同的转换,我们是通过在@Mapping注解指定的,而不同属性类型的转换,这是MapStruct的默认配置。个人感觉这个很好,很强大

2.3 使用Mapper

CarMapperTest.java

public class CarMapperTest {
    @Test
    public void shouldMapCarToDto() {
        Car car = new Car( "Morris", 5, CarType.SEDAN );
        CarDto carDto = CarMapper.INSTANCE.carToCarDto( car );
        Assert.assertNotNull(carDto);
        Assert.assertEquals(carDto.getMake(),"Morris");
        Assert.assertEquals(carDto.getSeatCount() ,5);
        Assert.assertEquals(carDto.getType() ,"SEDAN");
    }
 
}

运行这个单元测试,如果没有报错的话,就说明我们已经成功运行这个案例了!