mapStruct是一个很好用的字段映射工具,可以帮你自动生成代码完成字段映射。但有时需要在某些映射方法之前或之后应用自定义逻辑。
比如我们从数据库查出来的sex字段是一个数字 0,1,我们需要将其转换为男,女 放入Dto传给前端。我之前的做法是,mapstruct做完映射之后再进行字段转换。但这样会让service层增加很多set get 代码,如果是集合之间进行转换,我们还需遍历一次。
MapStruct 提供了两种方法:装饰器允许对特定映射方法进行类型安全的定制,以及映射前和映射后生命周期方法允许对给定源或目标类型的映射方法进行通用定制,可以很好的帮助我们解决该问题
mapstruct使用装饰器模式,对其映射后的结果做附加操作。
使用装饰器需要将装饰器应用于映射器类,使用@DecorateWith指定装饰器类
@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {
PersonMapper INSTANCE = Mappers.getMapper( PersonMapper.class );
PersonDto personToPersonDto(Person person);
AddressDto addressToAddressDto(Address address);
}
装饰器类必须实现映射器接口,可以声明为一个抽象类,只实现那些想要映射的方法。
使用示例
public abstract class PersonMapperDecorator implements PersonMapper {
private final PersonMapper delegate;
public PersonMapperDecorator(PersonMapper delegate) {
this.delegate = delegate;
}
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setFullName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
如果你的映射类使用了Spring容器,那么在装饰器类,你需要用@Qualifier(“delegate”)来注入该映射类
(名字必须是delegate,因为生成的实现类bean名称默认就是这个)
public abstract class PersonMapperDecorator implements PersonMapper {
@Autowired
@Qualifier("delegate")
private PersonMapper delegate;
@Override
public PersonDto personToPersonDto(Person person) {
PersonDto dto = delegate.personToPersonDto( person );
dto.setName( person.getFirstName() + " " + person.getLastName() );
return dto;
}
}
装饰器类不需要我们去调用,使用的时候我们还像以前那样调用映射类就行。(建议使用构造器注入bean)
@Autowired
private PersonMapper personMapper;
public PersonDto getPersonInfo(Long personId) {
Person person = personDao.queryPerson(personId);
return personMapper.personToPersonDto(person);
}
在自定义映射器时,装饰器可能并不总是满足需求。
例如,如果你不仅需要对特定的几个方法执行自定义映射,还需要对映射特定超类型的所有方法执行自定义内容,
在这种情况下,你可以使用在映射开始之前或映射完成之后调用的回调方法.
使用示例
@Mapper
public abstract class VehicleMapper {
@BeforeMapping
protected void flushEntity(AbstractVehicle vehicle) {
// 可以在mapper类做映射前做一些操作
}
@AfterMapping
protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) {
result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );
}
public abstract CarDto toCarDto(Car car);
}
//mapper自动生成的代码
public class VehicleMapperImpl extends VehicleMapper {
public CarDto toCarDto(Car car) {
flushEntity( car );
if ( car == null ) {
return null;
}
CarDto carDto = new CarDto();
fillTank( car, carDto );
return carDto;
}
}
@BeforeMapping
和 @AfterMapping
注释的方法,如果有参数,则只有在方法
(有点绕,用上边的例子解释一下)
上面@BeforeMapping
注释的 flushEntity
方法的参数是AbstractVehicle
类型, toCarDto
中的参数Car
就是该类型的子类,能传进去,就会生成该方法。
而上边@AfterMapping
注释的方法有两个参数,可以看到一个加有@MappingTarget
注解,意思是映射目标,而toCarDto
的映射目标类就是CarDto
他是AbstractVehicleDto
的子类,达到可分配的条件,生成该方法。
上述方法不用满足第一个条件,因为他们返回值为null,
如果@BeforeMapping
和 @AfterMapping
注释的方法返回值不为null,则必须也有参数可分配才能生成。
方法调用的顺序主要由它们的变体决定:
@BeforeMapping@MappingTarget 在对源参数进行任何空检查和构造新的目标 bean 之前调用没有参数的方法。
@BeforeMapping@MappingTarget在构造新的目标 bean 后调用带有参数的方法。
@AfterMapping在最后一条return语句之前的映射方法的末尾调用方法。