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 以雇用另一个映射器。但是,由于 ConversionServiceconvert 方法与映射方法的 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,类型 CarCarDto 具有类型 wheel 的属性分别为 WheelWheelDto。然后我希望能够像这样定义另一个映射器:

@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 已经从父映射器创建了所有必要的较小映射器...

坦率地说,我怀疑您能否通过 MapStructConversionService 自动连接到生成的映射器中。您在问题中描述的方式(通过 uses 注释属性连接各个映射器),可能是 MapStruct 开箱即用的最佳方式。

但是,如果您绝对需要使用 ConversionService 对某些 DTO(例如如果您有一些遗留转换器,则您不想重构为映射器)。基本上,您可以结合使用 Mappers.getMapper 静态工厂来获取映射器接口中的 ConversionServicedefault 方法的实例,以使用 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 作为示例。