MapStruct Mapper как Spring Framework Converter - возможно ли идиоматическое использование? - PullRequest
4 голосов
/ 24 сентября 2019

Я хотел бы объединить MapStruct картографы с моделью преобразования Spring * .Поэтому я объявляю каждый Mapper интерфейс как расширение Converter:

@Mapper
public interface CarMapper extends Converter<Car, CarDto> {    
    @Override
    CarDto convert(Car car);    
}

в Spring. Затем я могу использовать компоненты mapper, вводя стандартный ConversionService:

class CarWarehouse {
    @Autowired
    private ConversionService conversionService;

    ...

    public CarDto getCarInformation(Car car) {
        return conversionService.convert(car, CarDto.class);
    }
}

Это хорошо работает, но мне интересно, есть ли способ избежать внедрения некоторых Mappers в другие напрямую через атрибут 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 как это:

@Mapper
public interface WheelMapper extends Converter<Wheel, WheelDto> {    
    @Override
    WheelDto convert(Wheel wheel);    
}

Прямо сейчас, я должен был бы добавить этот Mapper явно:

@Mapper(uses = WheelMapper.class)
public interface CarMapper extends Converter<Car, CarDto>

, который бы затем далсгенерированный CarMapperImpl an @Autowired член типа WheelMapper, который будет вызван для сопоставления атрибута 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;
    }
}

Ответы [ 2 ]

0 голосов
/ 28 сентября 2019

Честно говоря, я сомневаюсь, что вы можете добиться автоматического подключения ConversionService в сгенерированные преобразователи с помощью MapStruct.Способ, который вы описали в вопросе (который связывает отдельные средства отображения с атрибутом аннотации uses), вероятно, лучшее, что MapStruct может дать из коробки.

Однако есть обходной путь , если вам абсолютно необходимо использовать ConversionService для выполнения преобразования для некоторых DTO (например, если у вас есть несколько унаследованных преобразователей, которые вы не хотите преобразовывать в преобразователи).По сути, вы можете использовать комбинацию статической фабрики Mappers.getMapper для получения экземпляра метода ConversionService и default в интерфейсе преобразователя, чтобы использовать ConversionService instance:

@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;
    }
}
0 голосов
/ 27 сентября 2019

Вы можете просто пропустить прохождение WheelMapper полностью, когда у вас просто есть CarMapper, сгенерированный CarMapperImpl будет содержать логику для отображения Wheel <-> WheelDto.Нет необходимости передавать что-либо в 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;
}

Я пытался добиться разумного введенияConversionService до MapStruct, но я думаю, что это невозможно.Для достижения такого умения вам понадобится поддержка MapStruct.Он даже не рассматривает введение ConversionService.Может быть, пользовательский универсальный сопоставитель, который уже реализован и использует ConversionService, мог бы работать, но я не смог этого сделать!Хотя я не вижу никакой причины для этого, поскольку MapStruct уже создает все необходимые меньшие преобразователи из родительского преобразователя ...

...