mapstruct
好久没有去写博客了,今天突然使用到了一些相对高级的mapstruct用法,所以总结一下。本篇文章带你走进mapstruct,从实际生产角度去灵活的使用这个强大的工具
置顶还是要贴上官网地址滴,毕竟官网是最详细的:mapstruct官网
1 什么是mapstruct
mapstruct
是一个对象映射工具将对象相互转换。举个例子,如果你有使用过DTO、VO这些鬼东西那么你一定知道,在对象之间进行转换是一件比较麻烦的事情,下面构造两个对象,他们的属性如下所示,
public class UserVo{
private String username;
private String password;
private String email;
}
public class UserDTO {
private String username;
private String email;
}
传统的方式我们会使用这些方法去实现这两个对象之间的转换:
- 构造对应的对象,例如
new UserVo()
,然后把UserDTO
中的属性值一个个的set
到UserVo
中去,这个过程有多痛苦就不用我多说了 - 再高级点的呢就是利用
BeanUtils
进行转换,例如BeanUtils.copyProperties(userVo,userDTO)
,省去了很多过程。但是BeanUtils
是基于反射做的对象属性映射,效率是不高的 - 此时我们思考,有没有什么办法,去通过set的方式给对象赋值因为此方式比反射的效率要高,且程序员还不用去写那么多代码呢?于是
mapstruct
就来了
2 mapstruct基础使用
maven中pom.xml引入座标和插件
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- <plugin> 如果是springboot项目就放开
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
现在我们还是使用第一节构造的两个对象,来实现对象的转换,使用的方法比较简单,创建UserMapper接口
@Mapper(componentModel = "spring") // 如果使用的是spring,那么mapstruct可以将自身添加到spring容器中,实现注入使用
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper( UserMapper.class ); // 如果使用spring则不需要去实例化该对象,而是从容器中获取
@Mappings({})
UserVo dto2Vo(UserDTO userDTO);
@Mappings({})
UserDTO vo2DTO(UserVo userVo);
}
使用:UserDTO
to UserVo
public class Test {
public static void main(String[] args) {
UserDTO userDTO = new UserDTO("username","password","email");
System.out.println(UserMapper.INSTANCE.dto2Vo(userDTO))
}
}
// 输出结果为username: 'username',email:'email'
如果是UserVo
转换为UserDTO
呢,因为UserVo
的字段是比UserDTO
字段少的,所以UserDTO
的password
属性为null
如果你细心的话,你会看到在你的target目录下,自动编译生成了UserMapperImpl.class
,打开看看你就知道了其中的道理
3 高级使用
3.1 @Mapping注解
在上述的例子我们是没有写@Mapping注解的,用一个空数组代替了,其实实际使用的时候我们免不了要去编写@Mapping
注解去自定义目标字段的映射操作。
@Mapping
少不了的就是target
属性,意思是目标字段
下面举个例子更方便的理解,javabean如下,为了便于理解source和target属性我对字段名进行了区分:
public class CarDTO{
private String brand;
private Double price;
private User user;
}
public class CarVO {
private String voBrand;
private Double voPrice;
private String customer; // 购买人
}
public class User {
private String username;
private Integer age;
}
还是将CarDTO
转换为CarVo
,我们编写一个CarMapper
类,这时候如果不使用@Mapping注解就无法很好的将属性进行赋值了:
@Mapper(componentModel = "spring") // 如果使用的是spring,那么mapstruct可以将自身添加到spring容器中,实现注入使用
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); // 如果使用spring则不需要去实例化该对象,而是从容器中获取
@Mappings({
@Mapping(source = "brand",target = "voBrand"),
@Mapping(source = "price",target = "voPrice"),
@Mapping(source = "user.username",target = "customer")
})
CarVo dto2Vo(CarDTO carDTO);
}
通过上述的例子就可以实现CarDTO到CarVo的转换操作,这是一种比较常见的情况,没有其他的操作只是多了一层嵌套
3.2 Java Expression
@Mapping还有一个属性十分重要就是expression,通过expression可以更灵活的对字段进行转换操作。
可以通过expression="java()"
的方式去调用java代码,例如调用某个utils的方法,我们可以这样使用expression=java(com.github.test.Utils.getValue())
举个例子,例如我们需要对的UserVo
的customer
字段值进行一个逻辑判断,如果年龄大于18就返回成年人,否则返回未成年。这时候我们就不得不使用expression来进行操作啦,语法格式也是很简单。直接看代码如下所示:
记住target属性是必须的
@Mapper(componentModel = "spring") // 如果使用的是spring,那么mapstruct可以将自身添加到spring容器中,实现注入使用
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); // 如果使用spring则不需要去实例化该对象,而是从容器中获取
@Mappings({
@Mapping(source = "brand",target = "voBrand"),
@Mapping(source = "price",target = "voPrice"),
@Mapping(expression = "java(CarMapper.getCustomer(carDTO.getUser().getAge()))",target = "customer")
})
CarVo dto2Vo(CarDTO carDTO);
static String getCustomer(Integer age) {
return age>18 ? "成年人" : "未成年";
}
}
3.3 可能遇到的问题
使用的时候不遇到问题是不可能的,你可能会遇到:
- 在有多个返回类型为同一类型,输入参数为同一类型时,例如
String getUsername(String username)
和String getPassword(String password)
这两个方法,你在编译的时候就会报错。因为你没有标识它们,mapstruct提供了@Named
注解,根据名称去区分不同的方法从而实现编译,然后引用的时候使用qualifiedByName
属性进行使用,如下所示:
@Mapper(componentModel = "spring") // 如果使用的是spring,那么mapstruct可以将自身添加到spring容器中,实现注入使用
public interface CarMapper {
CarMapper INSTANCE = Mappers.getMapper( CarMapper.class ); // 如果使用spring则不需要去实例化该对象,而是从容器中获取
@Mappings({
@Mapping(source = "brand",target = "voBrand"),
@Mapping(source = "price",target = "voPrice"),
@Mapping(target = "customer", source="age" ,qualifiedByName = "customer")
})
CarVo dto2Vo(CarDTO carDTO);
@Named("customer")
static String getCustomer(Integer age) {
return age>18 ? "成年人" : "未成年";
}
@Named("brand")
static String getBrand(Integer age) { // 随意举的例子,造成入参和返回值都是同一类型的情况
return "hahah";
}
}