MapStruct Mapper as Spring Framework Converter - idiomatic use possible?

丶灬走出姿态 提交于 2021-02-04 13:08:23

问题


I'd like to combine MapStruct mappers with Spring's Conversion model. So I declare every Mapper interface as an extension of Spring's Converter:

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

I can then use the mapper beans by injecting the standard ConversionService:

class CarWarehouse {
    @Autowired
    private ConversionService conversionService;

    ...

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

This works nicely, but I'm wondering whether there's a way to avoid injecting some Mappers into others directly via the uses attribute. What I'd like to do is tell a Mapper to use the ConversionService for employing another mapper. However, since the ConversionService's convert method doesn't match MapStruct's standard pattern for a mapping method, the code generation plugin doesn't recognise that it can use the service when looking for a submapping. Basically, what I want to do is write

@Mapper(uses=ConversionService.class)
public interface ParentMapper extends Converter<Parent, ParentDto>

instead of

@Mapper(uses={ChildMapper1.class, ChildMapper2.class, ChildMapper3.class})
public interface ParentMapper extends Converter<Parent, ParentDto>

Is there a way to achieve this?

Edit

Since it's been asked, let's say I've got a CarMapper defined as above, with the types Car and CarDto having an attribute wheel of type Wheel and WheelDto, respectively. Then I'd like to be able to define another Mapper like this:

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

Right now, I'd have to add this Mapper explicitly:

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

Which would then give the generated CarMapperImpl an @Autowired member of type WheelMapper which would be called in order to map the attribute wheel.

However, what I'd like is that the generated code would look somewhat like this:

@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;
    }
}

回答1:


It's been more than a year since I asked this question, but now we've come up with an answer inside the MapStruct project itself - the MapStruct Spring Extensions project.

A CarMapper example is provided as an example within the project.




回答2:


You can just skip passing a WheelMapper entirely, when you just have a CarMapper the generated CarMapperImpl will contain a logic to map Wheel <-> WheelDto as well. No need to pass anything to uses, making your issue obsolete.

carDto.setWheel( wheelToWheelDto( car.getWheel() ) );

with a method like;

protected WheelDto wheelToWheelDto(Wheel wheel) {
    if ( wheel == null ) {
        return null;
    }

    WheelDto wheelDto = new WheelDto();

    wheelDto.setName( wheel.getName() );

    return wheelDto;
}

I did try to achieve an intelligent injection of ConversionService through MapStruct, but it is not possible I think. You'd need support from MapStruct to achieve such a feat. It does not even consider injecting ConversionService. Maybe a custom generic mapper that is already implemented and uses ConversionService might work, but I was unable to do that! Though I don't see any reason for it since MapStruct is already creating all necessary smaller mappers from the parent mapper...




回答3:


Frankly, I doubt you can achieve automatic wiring of ConversionService into generated mappers by MapStruct. The way that you described in the question (that wires individual mappers through uses annotation attribute), probably, the best that MapStruct can give out of the box.

However, there is workaround, if you absolutely need to use ConversionService to perform conversion for some DTOs (e.g. if you have some legacy converters, that you don't want to refactor to mappers). Basically, you can use combination of Mappers.getMapper static factory to get instance of ConversionService and default method in the mapper interface, to use 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;
    }
}

Note: as you can see, workaround requires to write CarMapper code. So, in my opinion, the solution with uses annotation attribute is cleaner approach. For example, you get almost the same result, by defining following interface:

@Mapper(componentModel = "spring", 
        uses = {EngineMapper.class, WheelMapper.class}, 
        injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface CarMapper extends Converter<Car, CarDto> {
    @Override
    CarDto convert(Car car);

Generated mapper:

@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;
    }
}


来源:https://stackoverflow.com/questions/58081224/mapstruct-mapper-as-spring-framework-converter-idiomatic-use-possible

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!