MapStruct Mapper as Spring Framework Converter - 可以惯用吗?
MapStruct Mapper as Spring Framework Converter - idiomatic use possible?
我想将 MapStruct
映射器与 Spring 的 Conversion model 结合起来。所以我将每个 Mapper
接口声明为 Spring 的 Converter
:
的扩展
@Mapper
public interface CarMapper extends Converter<Car, CarDto> {
@Override
CarDto convert(Car car);
}
然后我可以通过注入标准 ConversionService
:
来使用映射器 bean
class CarWarehouse {
@Autowired
private ConversionService conversionService;
...
public CarDto getCarInformation(Car car) {
return conversionService.convert(car, CarDto.class);
}
}
这很好用,但我想知道是否有一种方法可以避免通过 uses
属性将某些映射器直接注入到其他映射器中。我想做的是告诉映射器 use
ConversionService
以雇用另一个映射器。但是,由于 ConversionService
的 convert
方法与映射方法的 MapStruct 标准模式不匹配,因此代码生成插件在查找子映射时无法识别它可以使用该服务。基本上,我想做的是写
@Mapper(uses=ConversionService.class)
public interface ParentMapper extends Converter<Parent, ParentDto>
而不是
@Mapper(uses={ChildMapper1.class, ChildMapper2.class, ChildMapper3.class})
public interface ParentMapper extends Converter<Parent, ParentDto>
有办法实现吗?
编辑
既然已经被问到,假设我有一个如上定义的 CarMapper
,类型 Car
和 CarDto
具有类型 wheel
的属性分别为 Wheel
和 WheelDto
。然后我希望能够像这样定义另一个映射器:
@Mapper
public interface WheelMapper extends Converter<Wheel, WheelDto> {
@Override
WheelDto convert(Wheel wheel);
}
现在,我必须明确地添加这个映射器:
@Mapper(uses = WheelMapper.class)
public interface CarMapper extends Converter<Car, CarDto>
然后会给生成的 CarMapperImpl
一个类型 WheelMapper
的 @Autowired
成员,它会被调用以映射属性 wheel
.
但是,我想要的是生成的代码看起来有点像这样:
@Component
public class CarMapperImpl implements CarMapper {
@Autowired
private ConversionService conversionService;
@Override
public CarDto convert(Car car) {
CarDto carDto = new CarDto();
carDto.setWheel(conversionService.convert(car.getWheel(), WheelDto.class);
return carDto;
}
}
您可以完全跳过传递 WheelMapper
,当您只有一个 CarMapper
时,生成的 CarMapperImpl
将包含映射 Wheel
<-> [ 的逻辑=16=] 还有。无需将任何内容传递给 uses
,使您的问题过时。
carDto.setWheel( wheelToWheelDto( car.getWheel() ) );
用类似的方法;
protected WheelDto wheelToWheelDto(Wheel wheel) {
if ( wheel == null ) {
return null;
}
WheelDto wheelDto = new WheelDto();
wheelDto.setName( wheel.getName() );
return wheelDto;
}
我确实尝试过通过MapStruct
实现对ConversionService
的智能注入,但我认为这是不可能的。您需要 MapStruct
的支持才能实现这一壮举。它甚至不考虑注入ConversionService
。也许已经实现并使用 ConversionService
的自定义通用映射器可能有效,但我无法做到这一点!虽然我没有看到任何原因,因为 MapStruct
已经从父映射器创建了所有必要的较小映射器...
坦率地说,我怀疑您能否通过 MapStruct
将 ConversionService
自动连接到生成的映射器中。您在问题中描述的方式(通过 uses
注释属性连接各个映射器),可能是 MapStruct
开箱即用的最佳方式。
但是,如果您绝对需要使用 ConversionService
对某些 DTO(例如如果您有一些遗留转换器,则您不想重构为映射器)。基本上,您可以结合使用 Mappers.getMapper
静态工厂来获取映射器接口中的 ConversionService
和 default
方法的实例,以使用 ConversionService
实例:
@Mapper(componentModel = "spring")
public interface CarMapper extends Converter<Car, CarDto> {
ConversionService CONVERSION_SERVICE = Mappers.getMapper(ConversionService.class);
@Override
default CarDto convert(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngine(CONVERSION_SERVICE.convert(car.getEngine(), EngineDto.class));
carDto.setWheel(CONVERSION_SERVICE.convert(car.getWheel(), WheelDto.class));
return carDto;
}
}
注意:如您所见,解决方法需要编写CarMapper
代码。因此,在我看来,带有 uses
注释属性的解决方案是更简洁的方法。例如,通过定义以下接口,您会得到几乎相同的结果:
@Mapper(componentModel = "spring",
uses = {EngineMapper.class, WheelMapper.class},
injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper extends Converter<Car, CarDto> {
@Override
CarDto convert(Car car);
生成的映射器:
@Component
public class CarMapperImpl implements CarMapper {
private final EngineMapper engineMapper;
private final WheelMapper wheelMapper;
@Autowired
public CarMapperImpl(EngineMapper engineMapper, WheelMapper wheelMapper) {
this.engineMapper = engineMapper;
this.wheelMapper = wheelMapper;
}
@Override
public CarDto convert(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngine(engineMapper.convert(car.getEngine()));
carDto.setWheel(wheelMapper.convert(car.getWheel()));
return carDto;
}
}
我问这个问题已经一年多了,但现在我们在 MapStruct 项目本身 - MapStruct Spring Extensions 项目中找到了答案。
在项目中提供了一个 CarMapper
example 作为示例。
我想将 MapStruct
映射器与 Spring 的 Conversion model 结合起来。所以我将每个 Mapper
接口声明为 Spring 的 Converter
:
@Mapper
public interface CarMapper extends Converter<Car, CarDto> {
@Override
CarDto convert(Car car);
}
然后我可以通过注入标准 ConversionService
:
class CarWarehouse {
@Autowired
private ConversionService conversionService;
...
public CarDto getCarInformation(Car car) {
return conversionService.convert(car, CarDto.class);
}
}
这很好用,但我想知道是否有一种方法可以避免通过 uses
属性将某些映射器直接注入到其他映射器中。我想做的是告诉映射器 use
ConversionService
以雇用另一个映射器。但是,由于 ConversionService
的 convert
方法与映射方法的 MapStruct 标准模式不匹配,因此代码生成插件在查找子映射时无法识别它可以使用该服务。基本上,我想做的是写
@Mapper(uses=ConversionService.class)
public interface ParentMapper extends Converter<Parent, ParentDto>
而不是
@Mapper(uses={ChildMapper1.class, ChildMapper2.class, ChildMapper3.class})
public interface ParentMapper extends Converter<Parent, ParentDto>
有办法实现吗?
编辑
既然已经被问到,假设我有一个如上定义的 CarMapper
,类型 Car
和 CarDto
具有类型 wheel
的属性分别为 Wheel
和 WheelDto
。然后我希望能够像这样定义另一个映射器:
@Mapper
public interface WheelMapper extends Converter<Wheel, WheelDto> {
@Override
WheelDto convert(Wheel wheel);
}
现在,我必须明确地添加这个映射器:
@Mapper(uses = WheelMapper.class)
public interface CarMapper extends Converter<Car, CarDto>
然后会给生成的 CarMapperImpl
一个类型 WheelMapper
的 @Autowired
成员,它会被调用以映射属性 wheel
.
但是,我想要的是生成的代码看起来有点像这样:
@Component
public class CarMapperImpl implements CarMapper {
@Autowired
private ConversionService conversionService;
@Override
public CarDto convert(Car car) {
CarDto carDto = new CarDto();
carDto.setWheel(conversionService.convert(car.getWheel(), WheelDto.class);
return carDto;
}
}
您可以完全跳过传递 WheelMapper
,当您只有一个 CarMapper
时,生成的 CarMapperImpl
将包含映射 Wheel
<-> [ 的逻辑=16=] 还有。无需将任何内容传递给 uses
,使您的问题过时。
carDto.setWheel( wheelToWheelDto( car.getWheel() ) );
用类似的方法;
protected WheelDto wheelToWheelDto(Wheel wheel) {
if ( wheel == null ) {
return null;
}
WheelDto wheelDto = new WheelDto();
wheelDto.setName( wheel.getName() );
return wheelDto;
}
我确实尝试过通过MapStruct
实现对ConversionService
的智能注入,但我认为这是不可能的。您需要 MapStruct
的支持才能实现这一壮举。它甚至不考虑注入ConversionService
。也许已经实现并使用 ConversionService
的自定义通用映射器可能有效,但我无法做到这一点!虽然我没有看到任何原因,因为 MapStruct
已经从父映射器创建了所有必要的较小映射器...
坦率地说,我怀疑您能否通过 MapStruct
将 ConversionService
自动连接到生成的映射器中。您在问题中描述的方式(通过 uses
注释属性连接各个映射器),可能是 MapStruct
开箱即用的最佳方式。
但是,如果您绝对需要使用 ConversionService
对某些 DTO(例如如果您有一些遗留转换器,则您不想重构为映射器)。基本上,您可以结合使用 Mappers.getMapper
静态工厂来获取映射器接口中的 ConversionService
和 default
方法的实例,以使用 ConversionService
实例:
@Mapper(componentModel = "spring")
public interface CarMapper extends Converter<Car, CarDto> {
ConversionService CONVERSION_SERVICE = Mappers.getMapper(ConversionService.class);
@Override
default CarDto convert(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngine(CONVERSION_SERVICE.convert(car.getEngine(), EngineDto.class));
carDto.setWheel(CONVERSION_SERVICE.convert(car.getWheel(), WheelDto.class));
return carDto;
}
}
注意:如您所见,解决方法需要编写CarMapper
代码。因此,在我看来,带有 uses
注释属性的解决方案是更简洁的方法。例如,通过定义以下接口,您会得到几乎相同的结果:
@Mapper(componentModel = "spring",
uses = {EngineMapper.class, WheelMapper.class},
injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper extends Converter<Car, CarDto> {
@Override
CarDto convert(Car car);
生成的映射器:
@Component
public class CarMapperImpl implements CarMapper {
private final EngineMapper engineMapper;
private final WheelMapper wheelMapper;
@Autowired
public CarMapperImpl(EngineMapper engineMapper, WheelMapper wheelMapper) {
this.engineMapper = engineMapper;
this.wheelMapper = wheelMapper;
}
@Override
public CarDto convert(Car car) {
if (car == null) {
return null;
}
CarDto carDto = new CarDto();
carDto.setEngine(engineMapper.convert(car.getEngine()));
carDto.setWheel(wheelMapper.convert(car.getWheel()));
return carDto;
}
}
我问这个问题已经一年多了,但现在我们在 MapStruct 项目本身 - MapStruct Spring Extensions 项目中找到了答案。
在项目中提供了一个 CarMapper
example 作为示例。