mapstruct
MapStruct 是一個(gè)屬性映射工具飒货,只需要定義一個(gè) Mapper 接口感凤,MapStruct 就會自動實(shí)現(xiàn)這個(gè)映射接口丸相,避免了復(fù)雜繁瑣的映射實(shí)現(xiàn)俯在。MapStruct官網(wǎng)地址: http://mapstruct.org/
MapStruct 使用APT生成映射代碼竟秫,其在效率上比使用反射做映射的框架要快很多。
mapstruct spring
MapStruct 結(jié)合spring使用跷乐,設(shè)定componentModel = "spring"
即可肥败,如下Mapper接口:
@Mapper(componentModel = "spring")
public interface CarDtoMapper{
Car dtoToEntity(CarDto dto);
}
生成的映射代碼如下,發(fā)現(xiàn)實(shí)現(xiàn)類上添加了@Component
注解
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2021-04-26T11:02:50+0800",
comments = "version: 1.4.2.Final, compiler: IncrementalProcessingEnvironment from gradle-language-java-6.8.jar, environment: Java 1.8.0_211 (Oracle Corporation)"
)
@Component
public class CarDtoMapperImpl implements CarDtoMapper {
@Override
public Car dtoToEntity(CarDto dto) {
if ( dto == null ) {
return null;
}
Car car = new Car();
car.setName( dto.getName() );
return car;
}
}
mapstruct spring 使用的缺點(diǎn)
mapstruct結(jié)合spring,在使用方式上主要是需要編寫接口文件和定義函數(shù)所帶來編碼工作量:
- 需要創(chuàng)建mapper接口文件劈猿,這個(gè)是mapstruct框架的必須要經(jīng)歷的過程,代碼量增加
- Dto和Entity之間互相轉(zhuǎn)換潮孽,需要在接口中添加一個(gè)方法揪荣,并且添加上
InheritInverseConfiguration
注解,如下
@InheritInverseConfiguration(name = "dtoToEntity")
CarDto entityToDto(Car dto);
- service 中依賴多個(gè)mapper轉(zhuǎn)換往史,增加構(gòu)造函數(shù)注入個(gè)數(shù)
- 覆蓋已有對象仗颈,需要添加如下map方法,如下
Car dtoMapToEntity(CarDto dto, @MappingTarget Car car)
反向映射,同樣需要添加如下方法
CarDto entityMapToDto(Car dto, @MappingTarget CarDto car);
理想的映射工具
對于對象映射挨决,有一種理想的使用方式请祖,偽代碼如下
Car car = mapper.map(dto, Car.class);
// or
Car car = new Car();
mapper.map(dto, car);
// 反向映射
CarDto dto = mapper.map(entity, CarDto.class);
// or
CarDto dto = new CarDto();
mapper.map(entity, dto);
只使用mapper對象,就可以解決任何對象之間的映射脖祈。
mapstruct 官方解決方案: mapstruct-spring-extensions
官方地址如下: https://github.com/mapstruct/mapstruct-spring-extensions
其思路是使用spring 的 Converter接口肆捕,官方用法如下
@Mapper(config = MapperSpringConfig.class)
public interface CarMapper extends Converter<Car, CarDto> {
@Mapping(target = "seats", source = "seatConfiguration")
CarDto convert(Car car);
}
@ComponentScan("org.mapstruct.extensions.spring")
@Component
static class AdditionalBeanConfiguration {
@Bean
ConfigurableConversionService getConversionService() {
return new DefaultConversionService();
}
}
@BeforeEach
void addMappersToConversionService() {
conversionService.addConverter(carMapper);
conversionService.addConverter(seatConfigurationMapper);
conversionService.addConverter(wheelMapper);
conversionService.addConverter(wheelsMapper);
conversionService.addConverter(wheelsDtoListMapper);
}
@Test
void shouldKnowAllMappers() {
then(conversionService.canConvert(Car.class, CarDto.class)).isTrue();
then(conversionService.canConvert(SeatConfiguration.class, SeatConfigurationDto.class)).isTrue();
then(conversionService.canConvert(Wheel.class, WheelDto.class)).isTrue();
then(conversionService.canConvert(Wheels.class, List.class)).isTrue();
then(conversionService.canConvert(List.class, Wheels.class)).isTrue();
}
@Test
void shouldMapAllAttributes() {
// Given
final Car car = new Car();
car.setMake(TEST_MAKE);
car.setType(TEST_CAR_TYPE);
final SeatConfiguration seatConfiguration = new SeatConfiguration();
seatConfiguration.setSeatMaterial(TEST_SEAT_MATERIAL);
seatConfiguration.setNumberOfSeats(TEST_NUMBER_OF_SEATS);
car.setSeatConfiguration(seatConfiguration);
final Wheels wheels = new Wheels();
final ArrayList<Wheel> wheelsList = new ArrayList<>();
final Wheel wheel = new Wheel();
wheel.setDiameter(TEST_DIAMETER);
wheel.setPosition(TEST_WHEEL_POSITION);
wheelsList.add(wheel);
wheels.setWheelsList(wheelsList);
car.setWheels(wheels);
// When
final CarDto mappedCar = conversionService.convert(car, CarDto.class);
}
使用 mapstruct-spring-extensions
,使用 ConfigurableConversionService
盖高, 雖然解決了使用同一個(gè)對象映射慎陵,但是代碼量沒有解決,同時(shí)喻奥,沒有提供覆蓋已有對象的使用方式
推薦 mapstruct-spring-plus
地址: https://github.com/ZhaoRd/mapstruct-spring-plus
這個(gè)項(xiàng)目參考了mapstruct-spring-extensions
項(xiàng)目席纽,同時(shí)使用APT技術(shù),動態(tài)生成Mapper
接口撞蚕,解決編寫接口的問題润梯,提供IObejctMapper
接口,提供所有的map方法甥厦。
maven引入
<properties>
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
<io.github.zhaord.version>1.0.1.RELEASE</io.github.zhaord.version>
</properties>
...
<dependencies>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
<version>${org.mapstruct.version}</version>
</dependency>
<dependency>
<groupId>io.github.zhaord</groupId>
<artifactId>mapstruct-spring-plus-boot-starter</artifactId>
<version>${io.github.zhaord.version}</version>
</dependency>
</dependencies>
...
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<annotationProcessorPaths>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
<path>
<groupId>io.github.zhaord</groupId>
<artifactId>mapstruct-spring-plus-processor</artifactId>
<version>${io.github.zhaord.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
gradle 引入
dependencies {
...
compile 'org.mapstruct:mapstruct:1.4.2.Final'
compile 'io.github.zhaord:mapstruct-spring-plus-boot-starter:1.0.1.RELEASE'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final'
testAnnotationProcessor 'org.mapstruct:mapstruct-processor:1.4.2.Final' // if you are using mapstruct in test code
annotationProcessor 'io.github.zhaord:mapstruct-spring-plus-processor:1.0.1.RELEASE'
testAnnotationProcessor 'io.github.zhaord:mapstruct-spring-plus-processor:1.0.1.RELEASE' // if you are using mapstruct in test code
...
}
使用案例
使用代碼如下
public enum CarType {
SPORTS, OTHER
}
@Data
public class Car {
private String make;
private CarType type;
}
@Data
@AutoMap(targetType = Car.class)
public class CarDto {
private String make;
private String type;
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
classes = {AutoMapTests.AutoMapTestConfiguration.class})
public class AutoMapTests {
@Autowired
private IObjectMapper mapper;
@Test
public void testDtoToEntity() {
var dto = new CarDto();
dto.setMake("M1");
dto.setType("OTHER");
Car entity = mapper.map(dto, Car.class);
assertThat(entity).isNotNull();
assertThat(entity.getMake()).isEqualTo("M1");
assertThat(entity.getCarType()).isEqualTo("OTHER");
}
@ComponentScan("io.github.zhaord.mapstruct.plus")
@Configuration
@Component
static class AutoMapTestConfiguration {
}
}
AutoMap 生成的接口代碼
@Mapper(
config = AutoMapSpringConfig.class,
uses = {}
)
public interface CarDtoToCarMapper extends BaseAutoMapper<CarDto, Car> {
@Override
@Mapping(
ignore = false
)
Car map(final CarDto source);
@Override
@Mapping(
ignore = false
)
Car mapTarget(final CarDto source, @MappingTarget final Car target);
}
mapstruct-spring-plus 帶來的便捷
- 使用
AutoMap
注解纺铭,減少了重復(fù)代碼的編寫,尤其是接口文件和映射方法 - 依賴注入矫渔,只需要注入
IObjectMapper
接口即可彤蔽,具體實(shí)現(xiàn)細(xì)節(jié)和調(diào)用方法,對客戶端友好 - 沒有丟失mapstruct的功能和效率
-
@Mapping
注解庙洼,都可以使用@AutoMapField
來完成字段的映射設(shè)置顿痪,因?yàn)?code>@AutoMapField繼承自@Mapping
,比如字段名稱不一致油够、跳過映射等