当前位置: 首页 > 后端技术 > Java

对象转换工具MapStruct简介

时间:2023-04-02 09:24:47 Java

前言在我们日常开发的分层结构应用中,为了将各层之间相互解耦,一般会定义不同的对象来实现不同层之间的数据传递。因此,随着XXXDTO、XXXVO、XXXBO等数据库对象派生出的各种对象,在不同层之间传输数据时,不可避免地经常需要这些对象相互转换。这时候一般有两种处理方式:①直接使用Setter和Getter方法进行转换,②使用一些工具类(如BeanUtil.copyProperties)进行转换。第一种方式,如果对象属性很多,需要写很多Getter/Setter代码。第二种方法虽然看起来比第一种方法简单很多,但是因为使用了反射,所以性能不是很好,使用中也有很多坑。今天要介绍的主角MapStruct在不影响性能的情况下解决了这两种方式的不足。什么是MapStructMapStruct是一个代码生成器,它基于约定优于配置的方法极大地简化了Javabean类型之间映射的实现。自动生成的映射转换代码仅使用简单的方法调用,因此速度快、类型安全且易于理解和阅读。Github地址MapStruct的源代码仓库。总的来说,它有以下三个特点:基于注解在编译时自动生成映射转换代码类型安全、高性能、独立的MapStruct使用步骤MapStruct使用起来比较简单,只需要以下三个步骤。①引入依赖(这里以Gradle方法为例)dependencies{implementation'org.mapstruct:mapstruct:1.4.2.Final'annotationProcessor'org.mapstruct:mapstruct-processor:1.4.2.Final'}②创建相关转换对象/***@authormghio*@since2021-08-08*/@DatapublicclassDoctor{privateIntegerid;私有字符串名称;}/***@authormghio*@since2021-08-08*/@DatapublicclassDoctorDTO{privateIntegerid;privateStringname;}③创建转换器类(Mapper)。需要注意的是,converter不一定非要以Mapper为结尾,但官方的例子建议以XXXMapper格式命名converter名称。下面是一个例子是最简单的映射情况(字段名和类型都完全匹配),只需要在转换器类上加上@Mapper注解即可,转换器代码如下:/***@authormghio*@since2021-08-08*/@MapperpublicinterfaceDoctorMapper{DoctorMapperINSTANCE=Mappers.getMapper(DoctorMapper.class);DoctorDTOtoDTO(Doctordoctor);}使用下面简单的测试来验证转换结果是否正确。测试代码如下:/***@authormghio*@since2021-08-08*/publicclassDoctorTest{@TestpublicvoidtestToDTO(){IntegerdoctorId=9527;字符串doctorName="mghio";医生doctor=newDoctor();医生.setId(医生ID);医生.setName(医生名);DoctorDTOdoctorDTO=DoctorMapper.INSTANCE.toDTO(医生);assertEquals(doctorId,doctorDTO.getId());assertEquals(doctorName,doctorDTO.getName());}}测试结果正常通过,说明使用DoctorMapper转换器达到了我们预期的效果。MapStruct实现分析在上面的例子中,使用MapStruct通过简单的三个步骤实现了Doctor到DoctorDTO的转换。那么,MapStruct是怎么做到的呢?其实通过我们定义的converter我们可以发现,converter是一个接口类型,而我们知道在Java中,接口是不能提供功能的,它只是定义了规范,具体的工作由它的实现类来完成。因此,我们可以大胆猜测,MapStruct一定是为我们定义的转换器接口(DoctorMapper)生成了一个实现类,而通过Mappers.getMapper(DoctorMapper.class)得到的转换器其实就是转换器接口的实现类。我们在测试类中调试验证一下:通过调试可以看出DoctorMapper.INSTANCE得到的是接口的实现类DoctorMapperImpl。这个转换器接口实现类是在编译时自动生成的。Gradle工程在build/generated/sources/anotationProcessor/Java下(Maven工程在target/generated-sources/annotations目录下),生成上面的例子转换器接口。实现类的源码如下:可以发现,自动生成的代码和我们平时手写的差不多,简单易懂,代码完全是编译生成的,没有运行时依赖。与使用反射的实现方式相比,调试源码定位错误容易,而反射定位问题要困难得多。常见使用场景介绍①对象属性名和类型完全一致从上面的例子可以看出,当属性名和类型完全一致时,我们只需要定义一个转换器接口,加上@Mapper注解即可,以及那么MapStruct会自动生成实现类来完成转换。示例代码如下:/***@authormghio*@since2021-08-08*/@DatapublicclassSource{privateIntegerid;私有字符串名称;}/***@authormghio*@since2021-08-08*/@DatapublicclassTarget{privateIntegerid;私有字符串名称;}/***@authormghio*@since2021-08-08*/@MapperpublicinterfaceSourceMapper{SourceMapperINSTANCE=Mappers.getMapper(SourceMapper.class);TargettoTarget(Sourcesource);}②同类型不同名称的对象属性当对象属性类型相同但属性名称不同时,通过@Mapping注解手动指定转换。示例代码如下:/***@authormghio*@since2021-08-08*/@DatapublicclassSource{privateIntegerid;privateStringsourceName;}/***@authormghio*@since2021-08-08*/@DatapublicclassTarget{privateIntegerid;privateStringtargetName;}/***@authormghio*@since2021-08-08*/@MapperpublicinterfaceSourceMapper{SourceMapperINSTANCE=Mappers.getMapper(SourceMapper.class);@Mapping(source="sourceName",target="targetName")TargettoTarget(Sourcesource);}③在Mapper中使用自定义的转换方法有时候,对于某些类型(例如:一个类的属性是自定义类),不能以自动生成代码的形式处理。这时候我们就需要自定义类型转换的方法。在JDK7之前的版本中,我们需要使用抽象类来定义转换Mapper。在JDK8以上的版本中,我们可以使用接口的默认方法来自定义类型转换的方法。示例代码如下:/***@authormghio*@since2021-08-08*/@DatapublicclassSource{privateIntegerid;私有字符串源名称;privateInnerSourceinnerSource;}/***@authormghio*@since2021-08-08*/@DatapublicclassInnerSource{privateIntegerdeleted;私有字符串名称;}/***@authormghio*@since2021-08-08*/@DatapublicclassTarget{privateIntegerid;私有字符串目标名称;privateInnerTargetinnerTarget;}/***@authormghio*@since2021-08-08*/@DatapublicclassInnerTarget{privateBooleanisDeleted;私有字符串名称;}/***@authormghio*@since2021-08-08*/@MapperpublicinterfaceSourceMapper{SourceMapperINSTANCE=Mappers.getMapper(SourceMapper.class);@Mapping(source="sourceName",target="targetName")TargettoTarget(Sourcesource);默认InnerTargetinnerTarget2InnerSource(InnerSourceinnerSource){如果(innerSource==null){returnnull;}InnerTargetinnerTarget=newInnerTarget();innerTarget.setIsDeleted(innerSource.getDeleted()==1);innerTarget.setName(innerSource.getName());返回内部目标;}}④多个对象转为一个对象返回在一些实际的业务编码过程中,难免会出现多个对象转为一个对象的情况,MapStruct也可以很好地支持。对于这种来自多个类的最终返回信息,我们可以通过配置实现多对一的转换。示例代码如下:/***@authormghio*@since2021-08-08*/@DatapublicclassDoctor{privateIntegerid;privateStringname;}/***@authormghio*@since2021-08-09*/@DatapublicclassAddress{privateStringstreet;privateIntegerzipCode;}/***@authormghio*@since2021-08-09*/@MapperpublicinterfaceAddressMapper{AddressMapperINSTANCE=Mappers.getMapper(AddressMapper.class);@Mapping(source="doctor.id",target="personId")@Mapping(source="address.street",target="streetDesc")DeliveryAddressDTOdoctorAndAddress2DeliveryAddressDTO(Doctor医生,地址address);}Converter从这个例子(AddressMapper)可以看出,当属性名和类型完全匹配时,也可以自动转换,但是当源对象有多个属性名和类型与目标对象完全相同时,仍然需要手动配置,因为MapStruct也无法准确确定应该使用哪种属性转换。获取转换器(Mapper)的几种方式获取转换器的方式根据@Mapper注解的componentModel属性不同,支持以下四种不同的值:default默认方法、默认方法、使用工厂方法(Mappers.getMapper(Class))获取cdi此时生成的mapper是一个应用范围的CDIbean。使用@Inject注解获取spring的Spring方法,可以通过@Autowired注解获取。该方法推荐在Spring框架jsr330生成mapper注解为@javax.inject.Named和@Singleton,通过@Inject获取①通过工厂方法获取上面的例子中,都是通过工厂方法获取的,即就是,使用Mappers.getMapper(Classclazz)方法获取指定类型的Mapper。那么调用的时候就不需要重复创建对象了。该方法的最终实现是通过我们定义接口的类加载器加载MapStruct生成的实现类(类名规则为:接口名+Impl),然后调用不带Constructor的类创建对象。核心源码如下:②使用依赖注入获取依赖注入(dependencyinjection),使用Spring框架开发的朋友应该不陌生,工作中也经常用到。MapStruct也支持使用依赖注入,官方推荐使用依赖注入获取。使用Spring依赖注入,只需要指定@Mapper注解的componentModel="spring"即可。示例代码如下:/***@authormghio*@since2021-08-08*/@Mapper(componentModel="spring")publicinterfaceSourceMapper{SourceMapperINSTANCE=Mappers.getMapper(SourceMapper.class);@Mapping(source="sourceName",target="targetName")TargettoTarget(Sourcesource);}我们之所以可以使用@Autowired来获取是因为SourceMapper接口的实现类已经注册为容器中的Bean。也可以看到通过如下生成的接口实现类的代码,自动给类添加了@Component注解。最后,有两个注意事项:①当两个转换对象的属性不一致时(比如Doctor对象中的某个字段在DoctorDTO中不存在),编译时会出现警告提示。可以在@Mapping注解中配置ignore=true,或者当有很多不一致的字段时,可以直接将@Mapper注解的unmappedTargetPolicy属性或unmappedSourcePolicy属性设置为ReportingPolicy.IGNORE。②如果你的项目中也使用了Lombok,需要注意Lombok的版本至少为1.18.10或以上,否则会编译失败。我刚开始用的时候也踩过这个坑。..小结本文介绍了对象转换工具Mapstruct库,以安全优雅的方式减少我们的转换代码。从文中的例子可以看出,Mapstruct提供了大量的功能和配置,可以让我们轻松快速地创建从简单到复杂的映射器。本文介绍的只是Mapstruct库的冰山一角。还有很多强大的功能本文没有提到。感兴趣的朋友可以自行查看官方使用指南。

最新推荐
猜你喜欢