简化mapstruct代码: mapstruct-spring-plus

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,在使用方式上主要是需要编写接口文件和定义函数所带来编码工作量:

  1. 需要创建mapper接口文件,这个是mapstruct框架的必须要经历的过程,代码量增加
  2. Dto和Entity之间互相转换,需要在接口中添加一个方法,并且添加上InheritInverseConfiguration注解,如下
@InheritInverseConfiguration(name = "dtoToEntity")    
CarDto entityToDto(Car dto);
  1. service 中依赖多个mapper转换,增加构造函数注入个数
  2. 覆盖已有对象,需要添加如下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 带来的便捷

  1. 使用AutoMap注解,减少了重复代码的编写,尤其是接口文件和映射方法
  2. 依赖注入,只需要注入IObjectMapper接口即可,具体实现细节和调用方法,对客户端友好
  3. 没有丢失mapstruct的功能和效率
  4. @Mapping注解,都可以使用@AutoMapField来完成字段的映射设置,因为@AutoMapField继承自@Mapping,比如字段名称不一致、跳过映射等
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,530评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,403评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,120评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,770评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,758评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,649评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,021评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,675评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,931评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,751评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,410评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,004评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,969评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,042评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,493评论 2 343

推荐阅读更多精彩内容