MapStruct 是一种代码生成器,它极大地简化了基于 “约定优于配置” 方法的 Java Bean 类型之间映射的实现。相对于其他 Bean 转换工具,MapStruct 生成的映射代码使用纯方法调用,因此快速、类型安全且易于理解。
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</dependency>
</dependencies>
基础使用示例
有如下两个实体类,代码中省略了 Getter、Setter 方法:
public class PersonDO {
private Integer id;
private String name;
private int age;
private Date birthday;
}
public class PersonDTO {
private Integer id;
private String userName;
private Integer age;
private Date birthday;
}
现通过 MapStruct 对这两个实体类进行数据转换:
// @Mapper 定义这是一个MapStruct对象属性转换接口,在这个类里面规定转换规则
// 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制
@Mapper
public interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mappings(@Mapping(source = "name", target = "userName"))
PersonDTO do2dto(PersonDO person);
}
测试结果如下:
public static void main(String[] args) {
PersonDO personDO = new PersonDO();
personDO.setId(1);
personDO.setName("xl");
personDO.setAge(25);
personDO.setBirthday(new Date());
PersonDTO personDTO = PersonConverter.INSTANCE.do2dto(personDO);
}
MapStruct 能够进行实体转换的原因,就是在编译期会自动生成一个实现了 PersonConverter 接口的子类,并为转换方法生成对应的 Setter、Getter 赋值代码。由于在编译期做了很多事情,所以 MapStruct 在运行期的性能会很好,并且还有一个好处,那就是可以把问题的暴露提前到编译期。使得如果代码中字段映射有问题,那么应用就会无法编译,强制开发者要解决这个问题才行。
1. @Mapping
通常情况下,MapStruct 会对属性名相同的字段自动做映射,不需要做额外配置,其中包括:
- 基本类型及其包装类型,如 int 和 Integer
- 基本类型的包装类型和 String 类型,如 String 和 Integer
- String 类型和枚举类型
但如果属性名称不同,则可以通过 @Mapping 注解的 source 和 target 来手动指定字段的映射逻辑:
@Mapping(source = "name", target = "userName")
如果想要为某个属性定义一个常量值,则可以使用 constant:
@Mapping(source = "name", constant = "xl")
如果属性类型不匹配,比如一个是 JSON 字符串,一个是实体对象。此时,我们还可以通过 expression 手动设置转换的自定义逻辑,不过目前只支持 Java 代码:
@Mapper
interface PersonConverter {
PersonConverter INSTANCE = Mappers.getMapper(PersonConverter.class);
@Mapping(target = "address",expression = "java(addressToString(dto2do.getAddress()))")
PersonDO dto2do(PersonDTO dto2do);
default String addressToString(Address address){
return JSON.toJSONString(address);
}
}
上面这种是自定义的类型转换,还有一些类型的转换是 MapStruct 本身就支持的,如 String 和 Date 之间的转换,MapStruct 底层会使用 SimpleDateFormat 按照 dateFormat 对其格式化:
@Mapping(target = "birthday", dateFormat = "yyyy-MM-dd HH:mm:ss")
如果想要忽略某个属性的赋值,则可以将 ignore 设为 true:
@Mapping(target = "id", ignore = true)
2. Map 转 Java Bean
如果有以下 Java Bean,省略 Setter、Getter 方法:
public class Customer {
private Long id;
private String name;
}
我们可以编写如下转换方法:
@Mapper
public interface CustomerMapper {
@Mapping(target = "name", source = "customerName")
Customer toCustomer(Map<String, String> map);
}
最终会生成类似如下的转换代码:
public class CustomerMapperImpl implements CustomerMapper {
@Override
public Customer toCustomer(Map<String, String> map) {
Customer customer = new Customer();
if ( map.containsKey( "id" ) ) {
customer.setId( Integer.parseInt( map.get( "id" ) ) );
}
if ( map.containsKey( "customerName" ) ) {
customer.setName( source.get( "customerName" ) );
}
}
}
不过需要注意,待转换的 Map 的 key 必须是 String 类型的,否则 MapStruct 转换代码会跳过这个 key。
3. 子类转换
假如有父类 Fruit 和两个子类 Apple 和 Banana 及其对应的 DTO 类,MapStruct 还支持以下转换代码:
@Mapper
public interface FruitMapper {
@SubclassMapping( source = AppleDto.class, target = Apple.class )
@SubclassMapping( source = BananaDto.class, target = Banana.class )
Fruit map( FruitDto source );
}
4. 使用 Spring 依赖注入
通过在 @Mapper 注解上指定 componentModel 属性为 spring 即可将实现类作为 Spring Bean 注入到容器中,这样在其他地方可以直接通过依赖注入使用。
@Mapper(componentModel = "spring")
public interface CustomerMapper {
@Mapping(source = "name", target = "customerName")
CustomerDto toCustomerDto(Customer customer);
}
看一下由 MapStruct 自动生成的类文件,会发现标记了 @Component 注解。
@Component
public class CustomerMapperImpl implements CustomerMapper {
//...
}