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,比如字段名称不一致、跳过映射等

简化mapstruct代码: mapstruct-spring-plus的更多相关文章

  1. lombok 简化java代码注解

    lombok 简化java代码注解 安装lombok插件 以intellij ide为例 File-->Setting-->Plugins-->搜索"lombok plug ...

  2. Lombok简化Java代码

    导包:import lombok.Data; Lombok简化Java代码: 在Lombok中,生成构造方法的annotation一共有三个:@NoArgsConstructor, @Required ...

  3. 基于MVC4+EasyUI的Web开发框架经验总结(11)--使用Bundles处理简化页面代码

    在Web开发的时候,我们很多时候,需要引用很多CSS文件.JS文件,随着使用更多的插件或者独立样式文件,可能我们的Web界面代码会越来越臃肿,看起来也很累赘,在MVC里面提供了一个Bundle的对象, ...

  4. 使用匿名委托,Lambda简化多线程代码

    使用匿名委托,Lambda简化多线程代码   .net中的线程也接触不少了.在多线程中最常见的应用莫过于有一个耗时的操作需要放到线程中去操作,而在这个线程中我们需要更新UI,这个时候就要创建一个委托了 ...

  5. 基于纯Java代码的Spring容器和Web容器零配置的思考和实现(3) - 使用配置

    经过<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(1) - 数据源与事务管理>和<基于纯Java代码的Spring容器和Web容器零配置的思考和实现(2) - ...

  6. 2018-08-20 中文代码之Spring Boot集成H2内存数据库

    续前文: 中文代码之Spring Boot添加基本日志, 源码库地址相同. 鉴于此项目中的数据总量不大(即使万条词条也在1MB之内), 当前选择轻量级而且配置简单易于部署的H2内存数据库比较合理. 此 ...

  7. Async/Await是这样简化JavaScript代码的

    译者按: 在Async/Await替代Promise的6个理由中,我们比较了两种不同的异步编程方法:Async/Await和Promise,这篇博客将通过示例代码介绍Async/Await是如何简化J ...

  8. 2018-08-24 中文代码之Spring Boot对H2数据库简单查询

    续前文: 中文代码之Spring Boot集成H2内存数据库 在词条中添加英文术语域: @Entity public class 词条 { @Id private long id; private S ...

  9. 2018-08-16 中文代码之Spring Boot添加基本日志

    之前中文代码之Spring Boot实现简单REST服务的演示服务不知为何中止. 新开issue: 演示服务中止 · Issue #2 · program-in-chinese/programming ...

随机推荐

  1. go中sync.Once源码解读

    sync.Once 前言 sync.Once的作用 实现原理 总结 sync.Once 前言 本次的代码是基于go version go1.13.15 darwin/amd64 sync.Once的作 ...

  2. 爬虫必知必会(7)_scrapy框架高级

    一.请求传参 实现深度爬取:爬取多个层级对应的页面数据 使用场景:爬取的数据没有在同一张页面中 在手动请求的时候传递item:yield scrapy.Request(url,callback,met ...

  3. Vue 插槽之插槽内容学习总结

    插槽内容使用方法介绍 父组件中引用支持插槽内容的子组件,形如以下(假设子组件为NavigationLink.vue) <navigation-link url="/profile&qu ...

  4. python之Click的简单应用

    一.介绍 Click是一个Python包,用于以可组合的方式创建漂亮的命令行界面,只需要很少的代码.这是"命令行界面创建工具包".它具有高度可配置性,但具有开箱即用的合理默认值. ...

  5. 在swoole中制作一款仿制laravel的框架

    首先需要确定一下思路:我希望基于swoole的扩展开发的代码在run起来的时候,在接收到ws或是tcp等消息时,自动路由到某个类上,同时类可以实现加载类的依赖注入功能.目前市面上占据主流的一款框架La ...

  6. 浅析MyBatis(二):手写一个自己的MyBatis简单框架

    在上一篇文章中,我们由一个快速案例剖析了 MyBatis 的整体架构与整体运行流程,在本篇文章中笔者会根据 MyBatis 的运行流程手写一个自定义 MyBatis 简单框架,在实践中加深对 MyBa ...

  7. C# 自定义时间进度条

    这篇文章对我帮助极大,我模仿着写了两遍大概摸清楚了自定义控件的流程.https://www.cnblogs.com/lesliexin/p/13265707.html 感谢大佬 leslie_xin ...

  8. Get和Post区别(转载)

    转载自:https://www.cnblogs.com/logsharing/p/8448446.html GET和POST是HTTP请求的两种基本方法,要说它们的区别,接触过WEB开发的人都能说出一 ...

  9. upx 手动脱壳

    查壳 UPX 0.89.6 - 1.02 / 1.05 - 2.90 (Delphi) stub -> Markus & Laszlo upx这类压缩壳手动脱壳非常简单. 一.查找oep ...

  10. salesforce零基础学习(一百零二)Limitation篇之 CPU Limit

    本篇参考: https://help.salesforce.com/articleView?id=000339361&type=1&mode=1 https://developer.s ...