属性映射工具——MapStruct(五)
目录:
这个应该是MapStruct系列的最后一篇了!!
一、重用映射配置
1.1、映射继承
mapStruct的映射继承,是通过@InheritConfiguration来实现的,这个前几篇已经提到过。这里就再不涉及了。但是需要再强调下:
1)方法级配置注解,例如@Mapping,@BeanMapping,@IterableMapping等等,都可以用注解@InheritConfiguration 继承方法的映射;
2)如果在mapStruct的作用域范围内,@InheritConfiguration继承的映射关系是明确的,唯一的,那么可以忽略其name属性;如果是不唯一,那么name属性必须配置,不然编译错误;
3)如果@InheritConfiguration继承的映射不满足要求,可以使用@Mapping、@BeanMapping、@IterableMapping 覆盖原有继承;
4)一般@InheritConfiguration是和@MappingTarget 配合使用的,类似如下:
@Mapper public interface CarMapper { @Mapping(target = "numberOfSeats", source = "seatCount") Car carDtoToCar(CarDto car); @InheritConfiguration void carDtoIntoCar(CarDto carDto, @MappingTarget Car car); }
1.2、逆映射
这个之前也提到过了,mapStruct的逆映射是通过 @InheritInverseConfiguration实现的,详细的例子可以看之前的系列文章,这里也是同样的再强调下:
1)如果A的结果类型与B的单一源类型相同,并且A的单一源类型与B的结果类型相同,则方法A被认为是方法B的反向方法。
2)逆继承的方法需要在当前的映射器(一个超类/接口)中定义。
3)如果有多个方法符合条件,则需要使用name属性指定继承配置的方法,如下所示:@InheritInverseConfiguration(name = "xxx")。
4)mapping中存在ignore、expression、constant的映射,逆映射后需要重新配置映射。
@Mapper public interface CarMapper { @Mapping(source = "numberOfSeats", target = "seatCount") CarDto carToDto(Car car); @InheritInverseConfiguration Car carDtoToCar(CarDto carDto); }
1.3、共享配置
共享配置是新知识。举个例子:我们经常在数据库中定义 创建时间date_create、修改时间date_update 这样的字段,通常情况下几乎每个表都存在这样的字段,数据库里面的类型是java.sql.Timestamp类型,而我们一般都转为String类型便于显示。如果不用mapStruct的共享配置,那相当于在每个表对应的转化类里面配置 Timestamp到String的映射。但是如果有共享配置,我们只要配置一遍,然后在其他地方引入,达到共享的目的。
mapStruct的共享映射通过注解 @MapperConfig定义,然后在@Mapper的属性config中引入。@MapperConfig注释具有与@Mapper注释相同的属性。任何未通过@Mapper指定的属性都将从共享配置中继承。在@Mapper中指定的属性优先于通过引用的配置类指定的属性。列表属性,如uses是一个简单地组合。
这个是官网的一个例子,我们可以看到,使用@mapper#config 引入后,uses,unmappedTargetPolicy 策略都继承了。
@MapperConfig( uses = CustomMapperViaMapperConfig.class, unmappedTargetPolicy = ReportingPolicy.ERROR ) public interface CentralConfig { } @Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) // Effective configuration: // @Mapper( // uses = { CustomMapperViaMapper.class, CustomMapperViaMapperConfig.class }, // unmappedTargetPolicy = ReportingPolicy.ERROR // ) public interface SourceTargetMapper { ... }
带有@MapperConfig 注释的接口还可以通过属性mappingInheritanceStrategy声明映射方法的原型,这些映射方法可用于从其中继承方法级映射注释。这样的原型方法并不意味着要实现或作为mapper API的一部分使用。
@MapperConfig( uses = CustomMapperViaMapperConfig.class, unmappedTargetPolicy = ReportingPolicy.ERROR, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG ) public interface CentralConfig { // Not intended to be generated, but to carry inheritable mapping annotations: @Mapping(target = "primaryKey", source = "technicalKey") BaseEntity anyDtoToEntity(BaseDto dto); } @Mapper(config = CentralConfig.class, uses = { CustomMapperViaMapper.class } ) public interface SourceTargetMapper { @Mapping(target = "numberOfSeats", source = "seatCount") // additionally inherited from CentralConfig, because Car extends BaseEntity and CarDto extends BaseDto: // @Mapping(target = "primaryKey", source = "technicalKey") Car toCar(CarDto car) }
当方法级映射配置注释从接口中的原型方法继承到映射器中的方法时,配置属性@Mapper#mappingInheritanceStrategy() / @MapperConfig#mappingInheritanceStrategy():
1)EXPLICIT(默认):只有当目标映射方法被注释为@InheritConfiguration,并且源和目标类型可以指定给原型方法的相应类型时,配置才会被继承,所有这些在映射配置继承中都有描述。
2)AUTO_INHERIT_FROM_CONFIG:如果目标映射方法的源和目标类型可以指定给原型方法的相应类型,那么配置将被自动继承。如果多个原型方法匹配,则必须使用@InheritConfiguration(name =…)来解决歧义。
二、自定义映射
有时需要在某些映射方法之前或之后应用自定义逻辑。MapStruct提供了两种方法:装饰器允许对特定映射方法进行类型安全的自定义,而映射前和映射后生命周期方法则允许对具有给定源或目标类型的映射方法进行通用的自定义。
2.1、使用装饰器自定义映射
不同的componentModel的装饰器形式略有不同,这里只讲componentModel = default 模式的装饰器,其他的可以参考官网。
装饰器用到了@DecoratedWith 这个注解,装饰器必须是装饰的映射器类型的子类型。可以定义为抽象类。该抽象类仅允许实现您要自定义的mapper接口的那些方法。对于所有未实现的方法,将使用默认生成例程生成对原始映射器的简单委托。
我们看个例子。我们将person转化成了personDto,这个没啥难度,都是之前的例子。
@Data @NoArgsConstructor @AllArgsConstructor public class Person { //主键 private String id; //名称 private String name; //省 private String province; //市 private String city; //生日 private Date birthday; //电话 private String phone; } @Data @NoArgsConstructor @AllArgsConstructor public class PersonDto { //主键 private String id; //名称 private String name; //地址(省+市) private String address; //生日 private Long birthday; //电话 private String phone; } @Mapper public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mappings({ @Mapping(target = "address",expression = "java(person.getProvince()+'-'+person.getCity())"), @Mapping(target = "birthday",expression = "java(person.getBirthday().getTime())") }) PersonDto toDto(Person person); } //PersonDto(id=001, name=张三, address=江苏省-南京市, birthday=1596109552343, phone=17600987654) @Test public void test1(){ Person person = new Person("001","张三","江苏省","南京市",new Date(),"17600987654"); PersonDto personDto = PersonMapper.INSTANCE.toDto(person); System.out.println(personDto); }
现在我们使用装饰者模式去增强——定义一个抽象类,实现映射器接口。目的是对phone进行模糊处理。看到没,这里相当于使用了一个代理,类似与Aop,我们可以在方法执行前,执行后,或者执行前后都对方法的执行进行增强。
//定义一个抽象类,实现映射器接口 public abstract class PersonMapperDecorator implements PersonMapper { private final PersonMapper delegate; public PersonMapperDecorator(PersonMapper delegate) { this.delegate = delegate; } @Override public PersonDto toDto(Person person) { PersonDto dto = delegate.toDto(person); //对电话号码进行加密 String phone = dto.getPhone(); if (StringUtils.isNotEmpty(phone) && phone.length() >= 11) { dto.setPhone(phone.substring(0, 3) + "*****" + phone.substring(7, phone.length() - 1)); } return dto; } }
在映射器里面使用@DecorateWith引入。
@Mapper @DecoratedWith(PersonMapperDecorator.class) public interface PersonMapper { PersonMapper INSTANCE = Mappers.getMapper(PersonMapper.class); @Mappings({ @Mapping(target = "address",expression = "java(person.getProvince()+'-'+person.getCity())"), @Mapping(target = "birthday",expression = "java(person.getBirthday().getTime())") }) PersonDto toDto(Person person); }
我们看看运行结果,是不是被增强了。
//PersonDto(id=001, name=张三, address=江苏省-南京市, birthday=1596109845802, phone=176*****765) @Test public void test1(){ Person person = new Person("001","张三","江苏省","南京市",new Date(),"17600987654"); PersonDto personDto = PersonMapper.INSTANCE.toDto(person); System.out.println(personDto); }
看下它的实现类。
//没有装饰器的 映射器实现类 @Generated
public class PersonMapperImpl_ implements PersonMapper { @Override public PersonDto toDto(Person person) { if ( person == null ) { return null; } PersonDto personDto = new PersonDto(); personDto.setId( person.getId() ); personDto.setName( person.getName() ); personDto.setPhone( person.getPhone() ); personDto.setBirthday( person.getBirthday().getTime() ); personDto.setAddress( person.getProvince()+'-'+person.getCity() ); return personDto; } } //装饰器进行引用 @Generated
public class PersonMapperImpl extends PersonMapperDecorator implements PersonMapper { private final PersonMapper delegate; public PersonMapperImpl() { this( new PersonMapperImpl_() ); } private PersonMapperImpl(PersonMapperImpl_ delegate) { super( delegate ); this.delegate = delegate; } }
2.2、@BeforeMapping与@AfterMapping自定义映射
//todo
三、null值处理
3.1、null值映射结果
//todo
3.2、null值检查
//todo
四、异常处理
//todo
参考文档:MapStruct 1.2.0.Final 参考指导书