日常开发中,我们经常会在PO,VO,DTO之间进行对象的转换,BeanUtils的copy方法很好,但是他不支持集合之间的转换,在网上查阅之后,发现了Mapstruct这个工具,使用之后,真香。
概述
MapStruct是一个Java注释处理器,用于生成类型安全的bean映射类。
您要做的就是定义一个映射器接口,该接口声明任何必需的映射方法。在编译期间,MapStruct将生成此接口的实现。此实现使用简单的Java方法调用在源对象和目标对象之间进行映射,即没有反射或类似内容。
与手动编写映射代码相比,MapStruct通过生成繁琐且易于出错的代码来节省时间。遵循配置方法上的约定,MapStruct使用合理的默认值,但在配置或实现特殊行为时不加理会。
与动态映射框架相比,MapStruct具有以下优点:
- 通过使用普通方法调用(settter/getter)而不是反射来快速执行
- 编译时类型安全性:只能映射相互映射的对象和属性,不能将order实体意外映射到customer DTO等。
- 如果有如下问题,编译时会抛出异常
- 映射不完整(并非所有目标属性都被映射)
- 映射不正确(找不到正确的映射方法或类型转换)
- 可以通过freemarker定制化开发
区别
工具 |
实现方式 |
缺点 |
说明 |
速度(100w) |
MapStruct |
getter/setter方法 |
需要了解注解和配置项语法 |
JSR269注解处理器在编译期自动生成Java Bean转换代码,支持可配置化,扩展性强 |
54ms |
orika |
动态生成字节码 |
首次调用耗时较久,性能适中 |
采用javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件 |
6764ms |
Spring BeanUtils |
反射机制 |
不支持名称相同但类型不同的属性转换 |
采用javassist类库生成Bean映射的字节码,之后直接加载执行生成的字节码文件 |
6700ms |
Apache BeanUtils |
反射机制 |
需要处理编译期异常,性能最差 |
|
7086ms |
dozer |
反射机制 |
性能差 |
使用reflect包下Field类的set(Object obj, Object value)方法进行属性赋值 |
6974ms |
BeanCopier |
反射机制 |
BeanCopier只拷贝名称和类型都相同的属性。即便基本类型与其对应的包装类型也不能相互转换 |
使用ASM的MethodVisitor直接编写各属性的get/set方法 |
6834ms |
性能排序:手动get/set > MapStruct > Spring BeanUtils > orika > BeanCopier > dozer > apache BeanUtils
常用注解

工具
可以在idea中搜索MapStruct Support工具来提高开发效率,可以通过点击source/target来直接查看字段之间的映射关系。

Mapstruct的使用
导入依赖
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.4.2.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-jdk8</artifactId> <version>1.4.2.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.4.2.Final</version> </dependency>
|
直接使用
@Mapper
@Mapper public abstract class CarConvert(){
public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class)
public abstract CarVo abc(CarDTo carDTo); }
|
测试
- 其中@Mapper的默认映射规则
- 同类型且同名的属性,会自动映射
- 且Mapstruct会自动进行类型的转换
@Test public void test(){ CarDTO carDTO = new CarDTO(); CarVO carVO = CarConvert.INSTANCE.abc(carDTO); System.out.println(carVO) }
|
@Mappings&@Mapping
@Mapper(componentModel = "spring") public abstract class CarConvert(){ public static CarConvert INSTANCE = Mappers.getMapper(CarConvert.class) @Mappings( value = { //指定金额的映射规则,数字格式化numberFormat,保留两位小数 @Mapping(source = "totalPrice",target = "totalPrice",numberFormat = "#.00"), //指定日期的映射规则,日期格式化dateFormat @Mapping(source = "publishDate",target = "publishDate",dateFormat = "yyyy-MM-dd HH:mm:ss"), //color属性不想映射的设置 @Mapping(target = "color",ignore = true) //属性名不同的映射 @Mapping(source = “brand”,target = “brandName”) //对象里面包含,另一个对象的映射处理 @Mapping(source = “driverDTO”,target = “driverVO”) } ) public abstract CarVo abc(CarDTo carDTo); @Mapping(source = “id”,target = “driverId”) @Mapping(source = “name”,target = “fullName”) public abstract DriverVO driverDTO2driverVO(DriverDTO driverDTO); @AfterMapping public void dto2voAfter(CarDTO carDTO, @MappingTarget CarVO carVO){ List<PartDTO> partDTOS = carDTO.getPartDTOS(); boolean hasPart = partDTOS != null && !partDTOS.isEmpty(); carVO.setHasPart(hasPart); } public abstract List<CarVo> dto2Vos(List<CarDTO> carDTO); @BeanMapping(ignoreByDefault = true) @Mapping(source = “id”,target = “id”) public abstract VehicleVO carDTO2vehicleVO(CarDTO carDTO); }
|
在Spring中使用
@Mapper
用@Mapper注解修饰一个转换类的接口。
@Mapper(componentModel = "spring") public interface GoodsMapper {
@BeanMapping(ignoreByDefault = true) @Mappings({ @Mapping(source = "goods.id", target = "id"), @Mapping(source = "goods.goodsName", target = "goodsName"), @Mapping(source = "goodsInformation.timeUnit", target = "timeUnit"), @Mapping(source = "goodsInformation.exchangeTimes", target = "exchangeTimes"), @Mapping(source = "goodsInformation.maxExchangeTimes", target = "maxExchangeTimes"), }) GoodsVO toGoodsVO(Goods goods, GoodsInfo goodsInfon); }
|
使用
在Service中注入Mapper
@Autowired private GoodsMapper goodsMapper
...
goodsMapper.toGoodsVO(goods,goodsInfon);
|
在编译之后,会自动在编译文件中对接口进行实现。
其他使用方法同上(用法一)
报错
遇到如下报错问题时,有两种解决方案
Unknown property "xxx" in result type xxx. Did you mean "null"?
方案一
代码生成器annotationProcessor
标签部分,将lombok放在mapstruct之前。
<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> <encoding>UTF-8</encoding> <!--自动生成代码annotationProcessor--> <annotationProcessorPaths> <path> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> </path> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>${mapstruct.version}</version> </path> </annotationProcessorPaths> </configuration> </plugin> </plugins> </build>
|
方案二
<!-- mapStruct-lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok-mapstruct-binding</artifactId> <version>0.2.0</version> <scope>provided</scope> </dependency>
|
高级用法
@AfterMapping&@MappingTarget
在映射最后一步对属性的自定义映射做处理
@AfterMapping public void dto2voAfter(CarDTO carDTO, @MappingTarget CarVO carVO){ List<PartDTO> partDTOS = carDTO.getPartDTOS(); boolean hasPart = partDTOS != null && !partDTOS.isEmpty(); carVO.setHasPart(hasPart); }
|
@BeanMapping
- ignoreByDefault:忽略mapstruct的默认映射行为。避免不需要的赋值、避免属性覆盖
@BeanMapping(ignoreByDefault = true) @Mapping(source = “id”,target = “id”) public abstract VehicleVO carDTO2vehicleVO(CarDTO carDTO);
|
@InheritConfiguration
- 可以继承**@Mapping,@BeanMapping,@IterableMapping**的映射规则
- 注解的方法上,有需要映射的字段,它会搜索有相同配置的映射,找到了直接复用此映射;若找到多个方法上都有满足此映射的配置,需要制定**@InheritConfiguration#name**的值,制定继承方法的映射。
更新的场景
避免同样的配置写多份
@InheritConfiguration @Mapping(source = “id”,target = “id”) public abstract void updateDto2vor(CarDTO carDTO, @MappingTarget CarVO carVO);
|
@InheritInverseConfiguration
当我们定义了一种对象到另一种对象的映射后,可以通过**@InheritInverseConfiguration **直接进行逆映射,只继承@Mapping注解配置,其他的注解配置不会继承。
@BeanMapping(ignoreByDefault = true) @InheritInverseConfiguration(name = "carDTO2vehicleVO") public abstract VehicleVO vehicleVOCarDTO2(CarDTO carDTO);
|
@InheritConfiguration的优先级高于@InheritInverseConfiguration。
共享配置
概述
@MapperConfig注解的接口就是共享配置,可以在@Mapper#config指定共享配置,@MapperConfig中的属性和@Mapper相同,@Mapper中的属性会覆盖@MapperConfig。
共享配置中可以设置原型映射,也可以是父类映射,再通过@MapperConfig、@Mapper的mappingInheritanceStrategy就可以实现原型映射的继承。
实例
Base
@Data @ToString public class BaseBO { private Long id; private String name; private String createTimeString; } @MapperConfig public interface BaseConfig { @Mapping(target = "createTimeString", source = "createTime") BaseBO toTestBaseBO(BasePO basePO); }
|
MappingInheritanceStrategy.EXPLICIT
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.EXPLICIT) public interface TestMapper { @InheritConfiguration void updateBO(TestFourPO testFourPO, @MappingTarget TestSixBO testSixBO); @InheritInverseConfiguration(name = "toTestBaseBO") void updatePO(TestSixBO testSixBO, @MappingTarget TestFourPO testFourPO); } @Component public class TestMapperImpl implements TestMapper { @Override public void updateBO(TestFourPO testFourPO, TestSixBO testSixBO) { if ( testFourPO == null ) { return; } if ( testFourPO.getCreateTime() != null ) { testSixBO.setCreateTimeString( new SimpleDateFormat().format( testFourPO.getCreateTime() ) ); } else { testSixBO.setCreateTimeString( null ); } testSixBO.setId( testFourPO.getId() ); testSixBO.setName( testFourPO.getName() ); } @Override public void updatePO(TestSixBO testSixBO, TestFourPO testFourPO) { if ( testSixBO == null ) { return; } try { if ( testSixBO.getCreateTimeString() != null ) { testFourPO.setCreateTime( new SimpleDateFormat().parse( testSixBO.getCreateTimeString() ) ); } else { testFourPO.setCreateTime( null ); } } catch ( ParseException e ) { throw new RuntimeException( e ); } testFourPO.setId( testSixBO.getId() ); testFourPO.setName( testSixBO.getName() ); } }
|
共享配置的原型映射并不会生成单独的实现方法。
虽然默认继承策略支持正/逆,但是引入共享配置,且mapper中正映射能继承原型映射的情况下,再设置逆映射方法,就必须制定name属性,否则同样有冲突报错。
MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_FROM_CONFIG) public interface TestMapper { void updateBO(TestFourPO testFourPO, @MappingTarget TestSixBO testSixBO); } @Component public class TestMapperImpl implements TestMapper { @Override public void updateBO(TestFourPO testFourPO, TestSixBO testSixBO) { if ( testFourPO == null ) { return; } if ( testFourPO.getCreateTime() != null ) { testSixBO.setCreateTimeString( new SimpleDateFormat().format( testFourPO.getCreateTime() ) ); } else { testSixBO.setCreateTimeString( null ); } testSixBO.setId( testFourPO.getId() ); testSixBO.setName( testFourPO.getName() ); } }
|
MappingInheritanceStrategy.AUTO_INHERIT_REVERSE_FROM_CONFIG
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG) public interface TestMapper { void updatePO(TestSixBO testSixBO, @MappingTarget TestFourPO testFourPO); } @Component public class TestMapperImpl implements TestMapper { @Override public void updatePO(TestSixBO testSixBO, TestFourPO testFourPO) { if ( testSixBO == null ) { return; } try { if ( testSixBO.getCreateTimeString() != null ) { testFourPO.setCreateTime( new SimpleDateFormat().parse( testSixBO.getCreateTimeString() ) ); } else { testFourPO.setCreateTime( null ); } } catch ( ParseException e ) { throw new RuntimeException( e ); } testFourPO.setId( testSixBO.getId() ); testFourPO.setName( testSixBO.getName() ); } }
|
MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG
@Mapper(config = BaseConfig.class, mappingInheritanceStrategy = MappingInheritanceStrategy.AUTO_INHERIT_ALL_FROM_CONFIG) public interface TestMapper { void updateBO(TestFourPO testFourPO, @MappingTarget TestSixBO testSixBO); void updatePO(TestSixBO testSixBO, @MappingTarget TestFourPO testFourPO); } @Component public class TestMapperImpl implements TestMapper { @Override public void updateBO(TestFourPO testFourPO, TestSixBO testSixBO) { if ( testFourPO == null ) { return; } if ( testFourPO.getCreateTime() != null ) { testSixBO.setCreateTimeString( new SimpleDateFormat().format( testFourPO.getCreateTime() ) ); } else { testSixBO.setCreateTimeString( null ); } testSixBO.setId( testFourPO.getId() ); testSixBO.setName( testFourPO.getName() ); } @Override public void updatePO(TestSixBO testSixBO, TestFourPO testFourPO) { if ( testSixBO == null ) { return; } try { if ( testSixBO.getCreateTimeString() != null ) { testFourPO.setCreateTime( new SimpleDateFormat().parse( testSixBO.getCreateTimeString() ) ); } else { testFourPO.setCreateTime( null ); } } catch ( ParseException e ) { throw new RuntimeException( e ); } testFourPO.setId( testSixBO.getId() ); testFourPO.setName( testSixBO.getName() ); } }
|