簡化mapstruct代碼: mapstruct-spring-plus

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ù)所帶來編碼工作量:

  1. 需要創(chuàng)建mapper接口文件劈猿,這個(gè)是mapstruct框架的必須要經(jīng)歷的過程,代碼量增加
  2. Dto和Entity之間互相轉(zhuǎn)換潮孽,需要在接口中添加一個(gè)方法揪荣,并且添加上InheritInverseConfiguration注解,如下
@InheritInverseConfiguration(name = "dtoToEntity")    
CarDto entityToDto(Car dto);
  1. service 中依賴多個(gè)mapper轉(zhuǎn)換往史,增加構(gòu)造函數(shù)注入個(gè)數(shù)
  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盖高, 雖然解決了使用同一個(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 帶來的便捷

  1. 使用AutoMap注解纺铭,減少了重復(fù)代碼的編寫,尤其是接口文件和映射方法
  2. 依賴注入矫渔,只需要注入IObjectMapper接口即可彤蔽,具體實(shí)現(xiàn)細(xì)節(jié)和調(diào)用方法,對客戶端友好
  3. 沒有丟失mapstruct的功能和效率
  4. @Mapping注解庙洼,都可以使用@AutoMapField來完成字段的映射設(shè)置顿痪,因?yàn)?code>@AutoMapField繼承自@Mapping,比如字段名稱不一致油够、跳過映射等
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蚁袭,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子石咬,更是在濱河造成了極大的恐慌揩悄,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鬼悠,死亡現(xiàn)場離奇詭異删性,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)焕窝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門蹬挺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人它掂,你說我怎么就攤上這事巴帮。” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵榕茧,是天一觀的道長垃沦。 經(jīng)常有香客問我,道長用押,這世上最難降的妖魔是什么肢簿? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮只恨,結(jié)果婚禮上译仗,老公的妹妹穿的比我還像新娘。我一直安慰自己官觅,他們只是感情好纵菌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著休涤,像睡著了一般咱圆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上功氨,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天序苏,我揣著相機(jī)與錄音,去河邊找鬼捷凄。 笑死忱详,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的跺涤。 我是一名探鬼主播匈睁,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼桶错!你這毒婦竟也來了航唆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤院刁,失蹤者是張志新(化名)和其女友劉穎糯钙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體退腥,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡任岸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了狡刘。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片享潜。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖颓帝,靈堂內(nèi)的尸體忽然破棺而出米碰,到底是詐尸還是另有隱情,我是刑警寧澤购城,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布吕座,位于F島的核電站,受9級特大地震影響瘪板,放射性物質(zhì)發(fā)生泄漏吴趴。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一侮攀、第九天 我趴在偏房一處隱蔽的房頂上張望锣枝。 院中可真熱鬧,春花似錦兰英、人聲如沸撇叁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽陨闹。三九已至,卻和暖如春薄坏,著一層夾襖步出監(jiān)牢的瞬間趋厉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工胶坠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留君账,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓沈善,卻偏偏與公主長得像乡数,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子矮瘟,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

推薦閱讀更多精彩內(nèi)容