问题
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