映射集合,流
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
注解分别拥有键值的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
注解来进行一些自定义映射操作。
例如下面定义两个枚举类型(不要在意合不合理拉)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| @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; } }
@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
| @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); }
@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
模式,将剩余的源枚举常量映射到指定的目标枚举常量上。
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
| public class DtoFactory {
public GoodsDto createGoodsDto(){ GoodsDto goodsDto = new GoodsDto(); goodsDto.setDescription("商户太懒拉,什么都没有写"); return goodsDto; } }
@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); }
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
| @AllArgsConstructor @NoArgsConstructor @Data public class Student {
private Integer id; private String firstName; private String lastName; }
@Data public class StudentDto {
private Integer id;
private String first_name;
private String last_name; }
@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); }
|
可以看到现在不同命名风格的字段也能够自动匹配映射拉~。
小憩一下
这一节介绍了MapStruct
是如何映射处理集合类、枚举类,以及自定义对象的创建方式和如何使用提供的SPI
接口来实现自己的定制化需求。下一节就来介绍如何使用APT来实现一个简单的MapStruct
的相关功能~