bean转换利器之mapstruct

前言

当开发java应用时,我们经常需要为同一业务定义不同的领域模型,如DTO用于数据传输,PO用于跟持久层打交道等等。当需要进行bean映射时,通常是采用频繁调用get,set方法进行赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class User {
private Long id;
private String name;
private String password;
private String mobile;
private Sex sex;
enum Sex {
male, female
}
//getter and setter
}
1
2
3
4
5
6
7
8
9
public class UserDTO {
private Long id;
private String name;
private String password;
private String mobile;
private User.Sex sex;
//getter and setter and toString
1
2
3
4
5
6
UserDTO userDTO = new UserDTO();
userDTO.setId(user.getId());
userDTO.setMobile(user.getMobile());
userDTO.setName(user.getName());
userDTO.setSex(user.getSex());
...

这种方式不但拉长了业务代码篇幅,不利于阅读,而且稍不留神就会写错。更严重的是,当模型中新增属性XXX时,很容易忘记在所有的bean转换时都补充setXXX,造成传值丢失。

mapstruct使用

mapstruct是个代码生成器,它通过在编译时生成相应的代码来帮助我们进行bean映射。

先来个demo实现UserDTO 和User的映射。

1.pom.xml引入mapstruct依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
<!-- IntelliJ does not pick up the processor if it is not in the dependencies.
There is already an open issue for IntelliJ see https://youtrack.jetbrains.com/issue/IDEA-150621
-->
<scope>provided</scope>
</dependency>

2.pom.xml中编译插件添加mapstruct注解处理器。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>1.2.0.Final</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>

3.新建bean转换接口。

1
2
3
4
5
6
7
8
9
10
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class );
UserDTO userToUserDto(User user);
}

4.调用userToUserDto方法,即完成了bean的映射。

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
User user =new User();
user.setId(1L);
user.setMobile("17767758345");
user.setSex(User.Sex.female);
user.setName("13123");
UserDTO dto = UserMapper.INSTANCE.userToUserDto(user);
System.out.println(dto.toString());
}

输出结果

1
UserDTO{id=1, name='13123', password='null', mobile='17767758345', sex=female}

此时你可能已经注意到UserMapper是个interface,它的实现正是由mapstruct在编译代码时生成,生成的位置在target/generated-sources

Snip20171105_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2017-11-05T20:20:40+0800",
comments = "version: 1.2.0.Final, compiler: javac, environment: Java 1.8.0_131 (Oracle Corporation)"
)
public class UserMapperImpl implements UserMapper {
@Override
public UserDTO userToUserDto(User user) {
if ( user == null ) {
return null;
}
UserDTO userDTO = new UserDTO();
userDTO.setId( user.getId() );
userDTO.setName( user.getName() );
userDTO.setPassword( user.getPassword() );
userDTO.setMobile( user.getMobile() );
userDTO.setSex( user.getSex() );
return userDTO;
}
}

这里只是做个简单介绍,更多mapstruct的使用姿势,参考。

官方doc
官方demo

常见问题

运行程序时,报错

1
java.lang.ClassNotFoundException: Cannot find implementation for com.gipplelake.framework.beanmapping.UserMapper

这是mapper的实现没有生成所致。
排查步骤:

  1. 检查target/generated-sources下有没有相应的mapperImpl。
  2. 执行mvn clean install再检查target/generated-sources
  3. mapper是否定义了@Mapper注解。
  4. 检查maven依赖。