持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第3天,点击查看活动详情
映射集合,流
Collection类
在MapStruct
中也是支持Collection类型之间的映射,使用方法与正常Bean对象映射是一致的,只是多了个循环语句。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface CollectionMapper {
Set<Integer> stringsToIntegerSet(Set<String> strings);
List<GoodsDto> goodsListToGoodsDtoList(List<Goods> goods);
GoodsDto toGoodsDto(Goods goods);
}
经过MapStruct
编译生成的实现类如下,可以看到确实相对于普通Bean对象映射就只是多了个循环语句。
@Component
public class CollectionMapperImpl implements CollectionMapper {
public CollectionMapperImpl() {
}
public Set<Integer> stringsToIntegerSet(Set<String> strings) {
if (strings == null) {
return null;
} else {
Set<Integer> set = new LinkedHashSet(Math.max((int)((float)strings.size() / 0.75F) + 1, 16));
Iterator var3 = strings.iterator();
while(var3.hasNext()) {
String string = (String)var3.next();
set.add(Integer.parseInt(string));
}
return set;
}
}
public List<GoodsDto> goodsListToGoodsDtoList(List<Goods> goods) {
if (goods == null) {
return null;
} else {
List<GoodsDto> list = new ArrayList(goods.size());
Iterator var3 = goods.iterator();
while(var3.hasNext()) {
Goods goods1 = (Goods)var3.next();
list.add(this.toGoodsDto(goods1));
}
return list;
}
}
public GoodsDto toGoodsDto(Goods goods) {
if (goods == null) {
return null;
} else {
GoodsDto goodsDto = new GoodsDto();
goodsDto.setId(goods.getId());
goodsDto.setPrice(goods.getPrice());
return goodsDto;
}
}
}
Map类
不止支持Collection
接口类型的映射,也是支持Map
接口类型的映射操作的。例如下面的示例代码,将一个Map<Long, LocalDateTime>
转换为Map<String,String>
类型。
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface MyMapMapping {
@MapMapping(valueDateFormat = "yyyy-MM-dd hh:mm:ss")
Map<String,String> toStringMap(Map<Long, LocalDateTime> source);
}
生成的实现类如下
@Component
public class MyMapMappingImpl implements MyMapMapping {
private final DateTimeFormatter dateTimeFormatter_yyyy_MM_dd_hh_mm_ss_0396102240 = DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss");
public MyMapMappingImpl() {
}
public Map<String, String> toStringMap(Map<Long, LocalDateTime> source) {
if (source == null) {
return null;
} else {
Map<String, String> map = new LinkedHashMap(Math.max((int)((float)source.size() / 0.75F) + 1, 16));
Iterator var3 = source.entrySet().iterator();
while(var3.hasNext()) {
Map.Entry<Long, LocalDateTime> entry = (Map.Entry)var3.next();
String key = (new DecimalFormat("")).format(entry.getKey());
String value = this.dateTimeFormatter_yyyy_MM_dd_hh_mm_ss_0396102240.format((TemporalAccessor)entry.getValue());
map.put(key, value);
}
return map;
}
}
@MapMapping
注解分别拥有键值的numberFormat
和dateFormat
的属性,使用方法同上一节讲的@Mapping
是一致的。
- keyDateFormat
- valueDateFormat
- keyNumberFormat
- valueNumberFormat
接口集合的实现类型
对于映射方法的返回类型是集合的接口类型时,MapStruct
将会选择其一个实现类来生成,具体生成类型如下表。
Interface type | Implementation type |
---|---|
Iterable | ArrayList |
Collection | ArrayList |
List | ArrayList |
Set | LinkedHashSet |
SortedSet | TreeSet |
NavigableSet | TreeSet |
Map | LinkedHashMap |
SortedMap | TreeMap |
NavigableMap | TreeMap |
ConcurrentMap | ConcurrentHashMap |
ConcurrentNavigableMap | ConcurrentSkipListMap |
枚举之间映射
MapStruct
支持将一个枚举类型转换为另一个枚举类型,默认情况下同名的枚举常量之间可以直接互相映射,如果枚举的常量名不同则需要使用@ValueMapping
注解来进行一些自定义映射操作。
例如下面定义两个枚举类型(不要在意合不合理拉)
// PayType.java
@Getter
public enum PayType {
UN_PAY(0),PAYING(1),PAYED(2),EXPIRE(3),OTHER_FAILURE(4);
private Integer type;
PayType(Integer type) {
this.type = type;
}
}
// PayDtoType.java
@Getter
@ToString
public enum PayDtoType {
UN_PAY(0),SUCCESS(1),FAILURE(2);
private Integer type;
PayDtoType(Integer type) {
this.type = type;
}
}
// PayMapper.java
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface PayMapper {
@ValueMappings({
@ValueMapping(source = "PAYING",target = "UN_PAY"),
@ValueMapping(source = "PAYED",target = "SUCCESS"),
@ValueMapping(source = "EXPIRE",target = "FAILURE"),
@ValueMapping(source = "OTHER_FAILURE",target = "FAILURE")
})
PayDtoType toPayDtoType(PayType payType);
}
// MapStruct生成的PayMapperImpl.java
@Component
public class PayMapperImpl implements PayMapper {
public PayMapperImpl() {
}
public PayDtoType toPayDtoType(PayType payType) {
if (payType == null) {
return null;
} else {
PayDtoType payDtoType;
switch (payType) {
case PAYING:
payDtoType = PayDtoType.UN_PAY;
break;
case PAYED:
payDtoType = PayDtoType.SUCCESS;
break;
case EXPIRE:
payDtoType = PayDtoType.FAILURE;
break;
case OTHER_FAILURE:
payDtoType = PayDtoType.FAILURE;
break;
case UN_PAY:
payDtoType = PayDtoType.UN_PAY;
break;
default:
throw new IllegalArgumentException("Unexpected enum constant: " + payType);
}
return payDtoType;
}
}
}
需要注意一点,如果源枚举类型的常量在目标枚举类型没有相同的名字,同时也没有使用@ValueMapping
注解去指定映射的常量,那么编译将会出错。不过MapStruct
还提供了MappingConstants.ANY_REMAINING
和MappingConstants.ANY_UNMAPPED
这两种模式来处理源枚举类型剩余/未指定的常量将其映射到指定的默认值上。
例如下面使用MappingConstants.ANY_REMAINING
模式,将剩余的源枚举常量映射到指定的目标枚举常量上。
@ValueMappings({
@ValueMapping(source = "PAYING",target = "UN_PAY"),
@ValueMapping(source = "PAYED",target = "SUCCESS"),
@ValueMapping(source = MappingConstants.ANY_REMAINING,target = "FAILURE")
})
PayDtoType toPayDtoTypeV2(PayType payType);
public PayDtoType toPayDtoTypeV2(PayType payType) {
if (payType == null) {
return null;
} else {
PayDtoType payDtoType;
switch (payType) {
case PAYING:
payDtoType = PayDtoType.UN_PAY;
break;
case PAYED:
payDtoType = PayDtoType.SUCCESS;
break;
case EXPIRE:
case OTHER_FAILURE:
default:
payDtoType = PayDtoType.FAILURE;
break;
case UN_PAY:
payDtoType = PayDtoType.UN_PAY;
}
return payDtoType;
}
}
自定义对象工厂
默认情况下,MapStruct
在生成方法的返回对象时,是通过调用返回对象的无参构造方法来实现的,不过也可以自定义对象工厂来实现别的对象创建方式,例如下面的使用例子。
// DtoFactory.java
public class DtoFactory {
public GoodsDto createGoodsDto(){
GoodsDto goodsDto = new GoodsDto();
goodsDto.setDescription("商户太懒拉,什么都没有写");
return goodsDto;
}
}
// GoodsMapper.java
@Mapper(uses = {DtoFactory.class})
public interface GoodsMapper {
GoodsMapper INSTANCE = Mappers.getMapper(GoodsMapper.class);
@Mapping(source = "goods.name",target = "goodsName")
@Mapping(source = "goods.id",target = "id")
GoodsDto toGoodsDto(Goods goods);
}
// GoodsMapperImpl.java
public class GoodsMapperImpl implements GoodsMapper {
private final DtoFactory dtoFactory = new DtoFactory();
public GoodsMapperImpl() {
}
public GoodsDto toGoodsDto(Goods goods) {
if (goods == null) {
return null;
} else {
GoodsDto goodsDto = this.dtoFactory.createGoodsDto();
goodsDto.setGoodsName(goods.getName());
goodsDto.setId(goods.getId());
goodsDto.setPrice(goods.getPrice());
return goodsDto;
}
}
}
通过@Mapper
的uses属性来注册对象工厂,同时你也可以在对象工厂的方法上添加@ObjectFactory
注解,这样就可以让源对象作为参数传入你的方法中
@ObjectFactory
public GoodsDto createGoodsDto(Goods goods){
GoodsDto goodsDto = new GoodsDto();
goodsDto.setDescription("商户太懒拉,什么都没有写");
return goodsDto;
}
自定义Naming Strategy
通常在Java中命名规则时是驼峰风格的,但有时因为祖传代码或者第三方接口对象等原因。定义的Bean对象并不总是驼峰风格的,而是下划线风格等,这个时候MapStruct默认的访问器命名策略就不能映射到了。除了手动一一声明@Mapping
注解之外,还有什么操作吗?当然是有滴,MapStruct提供了org.mapstruct.ap.spi.AccessorNamingStrategy
的SPI,从SPI的名字上也可以知道这是负责命名策略的。
创建processor模块
那么下面我们就来实现一下自己的NamingStrategy
,以便不同风格的属性名也可以被自动映射到。
下面就新建一个项目,引入相关依赖,并实现自定义的SPI,随后创建META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy文件,里面填写实现类的全限定名。
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.2</version>
</dependency>
</dependencies>
public class CustomAccessorNamingStrategy extends DefaultAccessorNamingStrategy {
@Override
public void init(MapStructProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
}
@Override
public String getPropertyName(ExecutableElement getterOrSetterMethod) {
String propertyName = super.getPropertyName(getterOrSetterMethod);
return NamingCase.toCamelCase(propertyName);
}
}
// META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy
pers.orange.processor.CustomAccessorNamingStrategy
测试processor模块
随后我们引入该模块测试一下效果
<path>
<groupId>pers.orange</groupId>
<artifactId>processor</artifactId>
<version>0.0.1-SNAPSHOT</version>
</path>
// Student.java
@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student {
private Integer id;
private String firstName;
private String lastName;
}
// StudentDto.java
@Data
public class StudentDto {
private Integer id;
private String first_name;
private String last_name;
}
// StudentMapper.java
@Mapper
public interface StudentMapper {
StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class);
StudentDto toStudentDto(Student student);
}
@Test
void testObjectFactory(){
Student student = new Student(1,"zijia","chen");
StudentDto studentDto = StudentMapper.INSTANCE.toStudentDto(student);
log.info("Dto:{}",studentDto); // Dto:StudentDto(id=1, first_name=zijia, last_name=chen)
}
可以看到现在不同命名风格的字段也能够自动匹配映射拉~。
小憩一下
这一节介绍了MapStruct
是如何映射处理集合类、枚举类,以及自定义对象的创建方式和如何使用提供的SPI
接口来实现自己的定制化需求。下一节就来介绍如何使用APT来实现一个简单的MapStruct
的相关功能~