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对象属性转换接口,在这个类里面规定转换规则// 在项目构建时,会自动生成改接口的实现类,这个实现类将实现对象属性值复制@Mapperpublic 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 代码:
@Mapperinterface 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;}
我们可以编写如下转换方法:
@Mapperpublic interface CustomerMapper {@Mapping(target = "name", source = "customerName")Customer toCustomer(Map<String, String> map);}
最终会生成类似如下的转换代码:
public class CustomerMapperImpl implements CustomerMapper {@Overridepublic 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 还支持以下转换代码:
@Mapperpublic 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 注解。
@Componentpublic class CustomerMapperImpl implements CustomerMapper {//...}
