大家好,我是小义,今天来讲一讲MapStruct。我们在写项目的过程中,分层式结构很常见,像表示层controller、业务逻辑层service、数据访问层dao等。
分层架构是软件工程中的一个基本原则,它帮助开发者构建更加灵活、可维护和可扩展的系统,甚至有人说:"计算机科学领域的任何问题都可以通过增加一个间接的中间层来解决,如果不行,那就加两层。
拷贝框架对比
但是分层就意味着我们要在各个层次之间做数据转换,所以在写代码的时候,经常看到各种PO、VO、DTO等实体。简单的对象转换,不管是用spring的BeanUtils,还是hutool的BeanUtil,基本上都足够了,但是和MapStruct相比,他们还是先天不足。
BeanUtils和BeanUtil底层用到的都是反射,目的是允许程序在运行时查询和操作对象的属性,同时给自身框架提供了扩展性,使其能够支持自定义的属性编辑器,非常灵活便捷。
而这也正是他们的缺点,反射操作通常比直接字段访问要慢,因为它涉及到更多的动态类型检查和方法调用。另外,当遇到复杂的转换时,像字段名不同,或者需要深拷贝,使用上述的工具类复制之后,还得重新写一堆Setter方法,而且代码很难复用。
接下来看看MapStruct,一个基于Java注解的代码生成器,它通过编译时的代码生成,避免了运行时的反射调用,从而使转换变得高效,还减少了运行时的错误,提高了代码的可维护性。MapStruct的使用方法简单直观,开发者只需定义映射接口,编译时自动生成实现类,大大减少了手动编写的样板代码。
当应用追求高性能,或者需要处理大量数据转换时,比起BeanUtils,MapStruct更有优势!
MapStruct具体用法
下面演示一下它的用法。首先需要在maven中引入相关依赖包。
代码语言:javascript复制<properties>
<lombok.version>1.18.24</lombok.version>
<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>
<target>1.8</target>
<annotationProcessorPaths>
<!--因为MapStruct需要在编译器生成转换代码,所以需要在maven-compiler-plugin插件中配置上对mapstruct-processor的引用-->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
定义一个映射的接口,该接口在编译完成后会自动生成一个实现类,包含不同实体对象之间的getter、setter属性赋值的代码。如下只需要写一个简单的方法就可以将PersonVO转换成PersonPO,其中特别注明了要将personVO的nickName赋值给personPO的name,忽略id。
代码语言:javascript复制@Mapper(componentModel = "spring")
public interface PersonMapStruct {
@Mappings({
@Mapping(target = "name", source = "nickName"),
@Mapping(target = "id", ignore = true) // 忽略id,不进行映射
})
PersonPO personVOToPersonPO(PersonVO personVO);
}
需要转换时,只需要下面几行代码。
代码语言:javascript复制PersonMapStruct mapper = Mappers.getMapper(PersonMapStruct.class);
PersonPO personPO = mapper.personVOToPersonPO(personVO);
当然,如果只是这样,有点为了用而用,显然不够。别急,往下看,当要对某些属性自定义转换的方法时,MapStruct的实现更加灵活。
如PersonVO和PersonPO里面都有个cats的列表属性,我们想实现深拷贝或是其他的特殊处理,可以像下面这样使用expression来指定要属性复制要所要运行的方法。
代码语言:javascript复制注意,不管是BeanUtil、BeanUtils,还是MapStruct,拷贝均是浅拷贝。
@Mapper(componentModel = "spring")
public interface PersonMapStruct {
@Mappings({
@Mapping(target = "name", source = "nickName"),
@Mapping(target = "cats", expression = "java(PersonMapStructRule.catList(personVO.getCats()))")
})
PersonPO personVOToPersonPO(PersonVO personVO);
}
代码语言:javascript复制public class PersonMapStructRule {
public static List<Cat> catList(List<Cat> catList) {
if (CollUtil.isNotEmpty(catList)) {
return catList.stream().map(item -> {
Cat cat = new Cat();
cat.setName(item.getName());
cat.setColor(item.getColor());
return cat;
}).collect(Collectors.toList());
}
return Lists.newArrayList();
}
}
实践下来,可以发现MapStruct用法比较简单,并且功能非常完善,可以应付各种情况的字段映射。因为是编译期就会生成真正的映射代码,所以MapStruct使得程序运行期的性能得到了大大的提升,对象拷贝时强烈推荐,秒杀BeanUtils,真的很香!!!