抱歉,您的瀏覽器無法訪問本站
本頁面需要瀏覽器支持(啟用)JavaScript
了解詳情 >

banner

映射集合,流

Collection类

MapStruct中也是支持Collection类型之间的映射,使用方法与正常Bean对象映射是一致的,只是多了个循环语句。

1
2
3
4
5
6
7
8
9
@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对象映射就只是多了个循环语句。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
@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>类型。

1
2
3
4
5
6
7
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
public interface MyMapMapping {


@MapMapping(valueDateFormat = "yyyy-MM-dd hh:mm:ss")
Map<String,String> toStringMap(Map<Long, LocalDateTime> source);
}

生成的实现类如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@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注解分别拥有键值的numberFormatdateFormat的属性,使用方法同上一节讲的@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注解来进行一些自定义映射操作。

例如下面定义两个枚举类型(不要在意合不合理拉)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 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;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 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_REMAININGMappingConstants.ANY_UNMAPPED这两种模式来处理源枚举类型剩余/未指定的常量将其映射到指定的默认值上

例如下面使用MappingConstants.ANY_REMAINING模式,将剩余的源枚举常量映射到指定的目标枚举常量上。

1
2
3
4
5
6
@ValueMappings({
@ValueMapping(source = "PAYING",target = "UN_PAY"),
@ValueMapping(source = "PAYED",target = "SUCCESS"),
@ValueMapping(source = MappingConstants.ANY_REMAINING,target = "FAILURE")
})
PayDtoType toPayDtoTypeV2(PayType payType);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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在生成方法的返回对象时,是通过调用返回对象的无参构造方法来实现的,不过也可以自定义对象工厂来实现别的对象创建方式,例如下面的使用例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 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注解,这样就可以让源对象作为参数传入你的方法中

1
2
3
4
5
6
@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文件,里面填写实现类的全限定名。

1
2
3
4
5
6
7
8
9
10
11
12
<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>
1
2
3
4
5
6
7
8
9
10
11
12
13
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);
}
}
1
2
// META-INF/services/org.mapstruct.ap.spi.AccessorNamingStrategy
pers.orange.processor.CustomAccessorNamingStrategy

测试processor模块

随后我们引入该模块测试一下效果

1
2
3
4
5
<path>
<groupId>pers.orange</groupId>
<artifactId>processor</artifactId>
<version>0.0.1-SNAPSHOT</version>
</path>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 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的相关功能~