mapstruct
MapStruct 是一個屬性對映工具,只需要定義一個 Mapper 介面,MapStruct 就會自動實現這個對映介面,避免了複雜繁瑣的對映實現。MapStruct官網地址: http://mapstruct.org/
MapStruct 使用APT生成對映程式碼,其在效率上比使用反射做對映的框架要快很多。
mapstruct spring
MapStruct 結合spring使用,設定componentModel = "spring"
即可,如下Mapper介面:
@Mapper(componentModel = "spring")
public interface CarDtoMapper{
Car dtoToEntity(CarDto dto);
}
生成的對映程式碼如下,發現實現類上新增了@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 使用的缺點
mapstruct結合spring,在使用方式上主要是需要編寫介面檔案和定義函式所帶來編碼工作量:
- 需要建立mapper介面檔案,這個是mapstruct框架的必須要經歷的過程,程式碼量增加
- Dto和Entity之間互相轉換,需要在介面中新增一個方法,並且新增上
InheritInverseConfiguration
註解,如下
@InheritInverseConfiguration(name = "dtoToEntity")
CarDto entityToDto(Car dto);
- service 中依賴多個mapper轉換,增加建構函式注入個數
- 覆蓋已有物件,需要新增如下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
, 雖然解決了使用同一個物件對映,但是程式碼量沒有解決,同時,沒有提供覆蓋已有物件的使用方式
推薦 mapstruct-spring-plus
地址: https://github.com/ZhaoRd/mapstruct-spring-plus
這個專案參考了mapstruct-spring-extensions
專案,同時使用APT技術,動態生成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
註解,減少了重複程式碼的編寫,尤其是介面檔案和對映方法 - 依賴注入,只需要注入
IObjectMapper
介面即可,具體實現細節和呼叫方法,對客戶端友好 - 沒有丟失mapstruct的功能和效率
@Mapping
註解,都可以使用@AutoMapField
來完成欄位的對映設定,因為@AutoMapField
繼承自@Mapping
,比如欄位名稱不一致、跳過對映等