老铁们,如果我们想方便地实现不同的对象属性的映射,你会怎么做?
纳尼?你只听说过BeanUtils相关的工具类?那你也太out了!!!

既然如此,老任就来给你介绍一个牛逼闪闪的代码生成器吧,绝对会让你欣喜若狂哦。

MapStruct是一个代码生成器,它基于配置方法的约定,极大地简化了 Java Bean类型之间属性映射的实现。应用程序通常需要在不同的对象模型之间进行映射,编写这样的映射代码是一项繁琐且容易出错的任务。MapStruct旨在通过尽可能自动化的方式来简化这项工作。
与其他映射框架相比,MapStruct在编译时,根据映射接口文件生成bean映射的实现类,这确保了高性能,使得开发人员能及时发现映射关系的错误并进行修正。
官网:https://mapstruct.org/
帮助文档:
https://mapstruct.org/documentation/stable/reference/html/

<dependencies>
<!-- mapstruct的jar -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.3.Final</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>
<!-- 用于自动生成映射关系接口的实现类 -->
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.3.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
mapstruct-processor的作用是在项目编译阶段,针对自定义的映射接口,自动生成对应的实现类。
类中设置属性,并给出getter和setter方法。
package com.qfedu.test;
public class User {
private Integer id;
private String name;
private String password;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}
该类中的属性名和User类中的表示相同数据的属性名一致,但是该类中没有password属性。
package com.qfedu.test;
public class UserVO {
private Integer id;
private String name;
private Integer age;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
}package com.qfedu.test;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
// 必须使用该注解修饰mapstruct的映射接口
@Mapper
public interface UserConverter {
// 返回映射接口的实例对象
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
/**
* 将User对象转为UserVO对象的方法
*/
UserVO convert(User user);
}
映射接口必须使用@Mapper进行修饰,该注解用于告知生成器自动生成映射接口的实现类。
Mappers.getMapper()不是必须的,在不通过依赖注入方式使用映射接口对象时使用。
package com.qfedu.test;
import java.time.LocalDateTime;
public class App {
public static void main(String[] args) {
User user = new User();
user.setId(1);
user.setUsername("哈哈");
user.setAge(30);
user.setCreateTime(LocalDateTime.now());
// 调用方法
UserVO userVO = UserConverter.INSTANCE.convert(user);
// 输出属性,进行验证
System.out.println(userVO .getId());
System.out.println(userVO .getName());
System.out.println(userVO .getAge());
}
}
}
怎么样?使用起来并不复杂吧!O(∩_∩)O哈哈~
MapStruct会自动生成UserConverter的实现类UserConverterImpl

代码如下所示:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.qfedu.test;
public class UserConverterImpl implements UserConverter {
public UserConverterImpl() {
}
public UserVO convert(User user) {
if (user == null) {
return null;
} else {
UserVO userVO = new UserVO();
userVO.setId(user.getId());
userVO.setName(user.getName());
userVO.setAge(user.getAge());
return userVO;
}
}
}
老铁们,看到实现类的代码后,是不是有种恍然大悟的感觉!原来是这个样子滴!


如果仅是这样简单的映射关系,也体现不出MapStruct的强大。接下来,老任带着大家一起飞!!!
理想很丰满,现实很骨感。很多时候,不同对象需要映射的属性名并不相同,这时,我们需要怎么配置映射关系呢?
我们将User类中的name属性修改为username属性,并重新生成其getter和setter方法。


当目标对象的属性名和源对象的属性名不同时,我们可以使用@Mapping注解设置属性的映射关系。
package com.qfedu.test;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
@Mappings({
@Mapping(source = "username", target = "name")
})
UserVO convert(User user);
}
source表示源对象中的属性(即User类中的属性)
target表示目标对象中的属性(即UserVO对象中的属性)

实体类中写那么多getter和setter方法,也不是个事呀!为了简化开发,我们一般会使用lombok。那么,在mapstruct中如何集成lombok呢?
在<annotationProcessorPaths>中增加如下配置:
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</path>
<!-- 如果使用lombok1.18.16版本,需要增加如下配置 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
最终的pom文件长这个样子:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qfedu</groupId>
<artifactId>mapstruct</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<org.mapstruct.version>1.5.3.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>1.5.3.Final</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
<scope>provided</scope>
</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>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.5.3.Final</version>
</path>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</path>
<!-- additional annotation processor required as of Lombok 1.18.16 -->
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-mapstruct-binding</artifactId>
<version>0.2.0</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
实体类中删除getter和setter方法,使用@Data注解修饰类。好了,大功告成!!!
package com.qfedu.test;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class UserVO {
private Integer id;
private String name;
private Integer age;
}
package com.qfedu.test;
import lombok.Data;
import java.time.LocalDateTime;
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
}
有些时候,我们是将多个对象的属性值赋值给另一个对象的属性,有了mapstruct,这都不叫事。
定义UserInfo类,类中包含用户和地址的属性:
@Data
public class UserInfo {
private Integer id;
private String username;
private Integer age;
private String province;
private String city;
private String detailAddress;
}
Address类如下:
@Data
public class Address {
private String province;
private String city;
private String detail;
}
User类如下:
@Data
public class User {
private Integer id;
private String username;
private String password;
private Integer age;
private LocalDateTime createTime;
}
package com.qfedu.test;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
@Mapper
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
@Mappings({
@Mapping(source = "username", target = "name"),
@Mapping(target = "createTime", expression = "java(convertToDate(user.getCreateTime()))")
})
UserVO convert(User user);
// 将多个源对象的属性赋值给目标对象
// 如果属性名不一致,可以通过"对象名.属性名"的语法进行映射
@Mapping(source = "address.detail", target = "detailAddress")
UserInfo convert(User user, Address address);
}
有的时候,源对象和目标对象中,需要映射的属性的类型不一致,这时,我们需要怎么处理呢?


User类中createTime类型为LocalDateTime,而UserVO中,createTime类型为Date,直接通过mapstruct进行转换时,转换结果不符合预期。
通过@Mapping的expression属性,调用进行类型转换的java代码。
package com.qfedu.test;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.time.ZoneId;
import java.util.Date;
// imports属性必须写
@Mapper(imports = {Date.class, ZoneId.class})
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
// expression 中,通过java()设置需要执行的java代码
@Mappings({
@Mapping(source = "username", target = "name"),
@Mapping(target = "createTime", expression = "java(Date.from(user.getCreateTime().atZone( ZoneId.systemDefault()).toInstant()))")
})
UserVO convert(User user);
}
由于这里用到了Date和ZoneId类,因此,需要在@Mapper注解中,通过imports属性,设置需要导入的类,否则无法自动生成映射接口的实现类。
自动生成的代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.qfedu.test;
import java.time.ZoneId;
import java.util.Date;
public class UserConverterImpl implements UserConverter {
public UserConverterImpl() {
}
public UserVO convert(User user) {
if (user == null) {
return null;
} else {
UserVO userVO = new UserVO();
userVO.setName(user.getUsername());
userVO.setId(user.getId());
userVO.setAge(user.getAge());
userVO.setCreateTime(Date.from(user.getCreateTime().atZone(ZoneId.systemDefault()).toInstant()));
return userVO;
}
}
}
在接口中自定义不同类型数据的转换方法,通过expression属性进行调用。
package com.qfedu.test;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Date;
@Mapper
public interface UserConverter {
UserConverter INSTANCE = Mappers.getMapper(UserConverter.class);
@Mappings({
@Mapping(source = "username", target = "name"),
@Mapping(target = "createTime", expression = "java(convertToDate(user.getCreateTime()))")
})
UserVO convert(User user);
// 接口中自定义转换方法
default Date convertToDate(LocalDateTime localDateTime) {
return Date.from(localDateTime.atZone(ZoneId.systemDefault()).toInstant());
}
}
本例中,定义了convertToDate()方法,实现LocalDateTime到Date的转换。然后,在@Mapping中,通过expression属性指定调用该方法。
自动生成的代码:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.qfedu.test;
public class UserConverterImpl implements UserConverter {
public UserConverterImpl() {
}
public UserVO convert(User user) {
if (user == null) {
return null;
} else {
UserVO userVO = new UserVO();
userVO.setName(user.getUsername());
userVO.setId(user.getId());
userVO.setAge(user.getAge());
userVO.setCreateTime(this.convertToDate(user.getCreateTime()));
return userVO;
}
}
}怎么样?老铁们是不是要赞一个:“MapStruct真香!”

如果你还有不明白的地方,可以在评论区或私信给我留言。关注Java架构栈,干货天天都不断哦。

原文作者:Java架构栈;如有侵权,请联系管理员删除。