Mapstruct it can replace BeanUtil to perform conversions between DTO, VO, PO. It uses the annotation processor mechanism of Java compilation period, to put it bluntly, it is a code generator, instead of you manually type conversion during the take value assignment operation.
1
2
3
4
5
|
@Mapper(componentModel = "spring")
public interface AreaMapping {
List<AreaInfoListVO> toVos(List<Area> areas);
}
|
In just a few lines, a collection of PO is transformed into a collection of corresponding VO.
1
2
3
4
5
6
7
8
|
// spring bean
@Autowired
AreaMapping areaMapping
// 转换源 areas
List<Area> areas = ……;
// 转换目标 vos
List<AreaInfoListVO> vos = areaMapping.toVos(areas)
|
But it’s still not very pleasant to write this way, and you have to inject the corresponding Mapper class every time.
Converter
Spring framework provides a Converter<S,T>
interface.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
@FunctionalInterface
public interface Converter<S, T> {
@Nullable
T convert(S source);
default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {
Assert.notNull(after, "After Converter must not be null");
return (s) -> {
T initialResult = this.convert(s);
return initialResult != null ? after.convert(initialResult) : null;
};
}
}
|
Its role is to convert S
to T
, which coincides with the role of Mapstruct.
The Converter
will be registered to the ConversionService
through the ConverterRegistry
registration interface, and then you can do the conversion through the convert
method of the ConversionService
.
1
|
<T> T convert(@Nullable Object source, Class<T> targetType);
|
MapStruct Spring Extensions
Based on the above mechanism, the official MapStruct Spring Extensions plugin was introduced, which implements a mechanism where all Mapstruct mapping interfaces ( Mapper ) that implement Converter
are automatically registered to ConversionService
. We only need to go through the ConversionService
to do any conversion operation.
1
2
3
4
5
6
7
8
9
10
|
/**
* @author felord.cn
* @since 1.0.0
*/
@Mapper(componentModel = "spring")
public interface CarMapper extends Converter<Car, CarDto> {
@Mapping(target = "seats", source = "seatConfiguration")
CarDto convert(Car car);
}
|
Usage..
1
2
3
4
5
6
|
@Autowired
private ConversionService conversionService;
Car car = ……;
CarDto carDto = conversionService.convert(car,CarDto.class);
|
MapStruct Spring Extensions will automatically generate an adapter class to handle Mapper Registration.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package org.mapstruct.extensions.spring.converter;
import cn.felord.mapstruct.entity.Car;
import cn.felord.mapstruct.entity.CarDto;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.convert.ConversionService;
import org.springframework.stereotype.Component;
/**
* @author felord.cn
* @since 1.0.0
*/
@Component
public class ConversionServiceAdapter {
private final ConversionService conversionService;
public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
this.conversionService = conversionService;
}
public CarDto mapCarToCarDto(final Car source) {
return (CarDto)this.conversionService.convert(source, CarDto.class);
}
}
|
Customization
Customize the package path and name of the adapter class
By default, the generated adapter class will be located in the package org.mapstruct.extensions.spring.converter
with a fixed name of ConversionServiceAdapter
. If you wish to change the package path or name, you can do so.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
package cn.felord.mapstruct.config;
import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;
/**
* @author felord.cn
* @since 1.0.0
*/
@MapperConfig(componentModel = "spring")
@SpringMapperConfig(conversionServiceAdapterPackage = "cn.felord.mapstruct.config",
conversionServiceAdapterClassName = "MapStructConversionServiceAdapter")
public class MapperSpringConfig {
}
|
Without specifying the conversionServiceAdapterPackage
element, the generated Adapter class will reside in the same package as the annotated Config, so the path above can be omitted.
Specifying a ConversionService
If you have more than one ConversionService
in your Spring IoC container, you can specify it via the conversionServiceBeanName
parameter of the @SpringMapperConfig
annotation.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
package cn.felord.mapstruct.config;
import org.mapstruct.MapperConfig;
import org.mapstruct.extensions.spring.SpringMapperConfig;
/**
* @author felord.cn
* @since 1.0.0
*/
@MapperConfig(componentModel = "spring")
@SpringMapperConfig(conversionServiceAdapterPackage = "cn.felord.mapstruct.config",
conversionServiceAdapterClassName = "MapStructConversionServiceAdapter",
conversionServiceBeanName = "myConversionService")
public class MapperSpringConfig {
}
|
Integrating Spring’s built-in conversions
Spring provides a lot of nice Converter<S,T>
implementations internally, some of them are not directly open, if you want to use them with the Mapstruct mechanism, you can register them with externalConversions
of the @SpringMapperConfig
annotation.
1
2
3
4
|
@MapperConfig(componentModel = "spring")
@SpringMapperConfig(
externalConversions = @ExternalConversion(sourceType = String.class, targetType = Locale.class))
public interface MapstructConfig {}
|
The corresponding conversions are automatically generated in the adapter.
1
2
3
4
5
6
7
8
9
10
11
12
|
@Component
public class ConversionServiceAdapter {
private final ConversionService conversionService;
public ConversionServiceAdapter(@Lazy final ConversionService conversionService) {
this.conversionService = conversionService;
}
public Locale mapStringToLocale(final String source) {
return conversionService.convert(source, Locale.class);
}
}
|
Summary
mapstruct-spring-annotations enables developers to use the defined Mapstruct mappers via ConversionService
without having to import each Mapper separately, thus allowing loose coupling between Mappers. It does not affect the Mapstruct mechanism itself.
Reference https://mp.weixin.qq.com/s/oZpZgqftZ0xeEFdrnUXgAw