Dozer 与 Spring 整合,我们可以使用其 DozerBeanMapperFactoryBean
,配置如下:
<bean class="org.dozer.spring.DozerBeanMapperFactoryBean"> <property name="mappingFiles" value="classpath*:/*mapping.xml"/> <!--自定义转换器--> <property name="customConverters"> <list> <bean class= "org.dozer.converters.CustomConverter"/> </list> </property> </bean>
DozerBeanMapperFactoryBean
支持设置属性比较多,可以自定义设置类型转换,还可以设置其他属性。
另外还有一种简单的方法,我们可以在 XML 中配置 DozerBeanMapper
:
<bean id="org.dozer.Mapper" class="org.dozer.DozerBeanMapper"> <property name="mappingFiles"> <list> <value>dozer/dozer-Mapperpping.xml</value> </list> </property> </bean>
Spring 配置完成之后,我们在代码中可以直接注入:
@Autowired Mapper mapper; public void objMapping(StudentDTO studentDTO) { // 直接使用 StudentDO studentDO = mapper.map(studentDTO, StudentDO.class); }
注解方式
Dozer 注解方式相比 XML 配置来说功能很弱,只能完成字段名不一致的映射。
上面的代码中,我们可以在 DTO 的 no
字段上使用 @Mapping
注解,这样我们在使用 Dozer 完成转换时,该字段属性将会被复制。
@Data public class StudentDTO { private String name; private Integer age; @Mapping("number") private String no; private List<String> subjects; private Course course; private String createDate; }
虽然目前注解功能有点薄弱,不过后看版本官方可能增加新的注解功能,另外 XML 与注解可以一起使用。
最后 Dozer 底层本质上还是使用了反射完成属性的复制,所以执行速度并不是那么理想。
orika
orika也是一个跟 Dozer 类似的重量级属性复制工具类,也提供诸如 Dozer 类似的功能。但是 orika 无需使用繁琐 XML 配置,它自身提供一套非常简洁的 API 用法,非常容易上手。
首先我们引入其最新的依赖:
<dependency> <groupId>ma.glasnost.orika</groupId> <artifactId>orika-core</artifactId> <version>1.5.4</version> </dependency>
基本使用方法如下:
// 省略其他设值代码 // 这里先不要设值时间 // studentDTO.setCreateDate("2020-08-08"); MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); MapperFacade mapper = mapperFactory.getMapperFacade(); StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);
这里我们引入两个类 MapperFactory
与 MapperFacade
,其中 MapperFactory
可以用于字段映射,配置转换器等,而 MapperFacade
的作用就与 Beanutils 一样,用于负责对象的之间的映射。
上面的代码中,我们故意注释了 DTO 对象中的 createDate 时间属性的设值,这是因为默认情况下如果没有单独设置时间类型的转换器,上面的代码将会抛错。
另外,上面的代码中,对于字段名不一致的属性,是不会复制的,所以我们需要单独设置。
下面我们就设置一个时间转换器,并且指定一下字段名:
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); ConverterFactory converterFactory = mapperFactory.getConverterFactory(); converterFactory.registerConverter(new DateToStringConverter("yyyy-MM-dd")); mapperFactory.classMap(StudentDTO.class, StudentDO.class) .field("no", "number") // 一定要调用下 byDefault .byDefault() .register(); MapperFacade mapper = mapperFactory.getMapperFacade(); StudentDO studentDO = mapper.map(studentDTO, StudentDO.class);
上面的代码中,首先我们需要在 ConverterFactory
注册一个时间类型的转换器,其次我们还需要再 MapperFactory
指定不同字段名的之间的映射关系。
这里我们要注意,在我们使用 classMap
之后,如果想要相同字段名属性默认被复制,那么一定调用 byDefault
方法。
简单对比一下 DTO 与 DO 对象:
上图可以发现 orika 的一些特性:
- 默认支持类型不一致(基本类型/包装类型)转换
- 支持深拷贝
- 指定不同字段名映射关系,属性可以被成功复制。
另外 orika 还支持集合映射:
MapperFactory mapperFactory = new DefaultMapperFactory.Builder().build(); List<Person> persons = new ArrayList<>(); List<PersonDto> personDtos = mapperFactory.getMapperFacade().mapAsList(persons, PersonDto.class);
最后聊下 orika 实现原理,orika 与 dozer 底层原理不太一样,底层其使用了 javassist 生成字段属性的映射的字节码,然后直接动态加载执行字节码文件,相比于 Dozer 的这种使用反射原来的工具类,速度上会快很多。
MapStruct
不知不觉,一口气已经写了 5 个属性复制工具类,小伙伴都看到这里,那就不要放弃了,坚持看完,下面将介绍一个与上面这些都不太一样的工具类「MapStruct」。
上面介绍的这些工具类,不管使用反射,还是使用字节码技术,这些都需要在代码运行期间动态执行,所以相对于手写硬编码这种方式,上面这些工具类执行速度都会慢很多。
那有没有一个工具类的运行速度与硬编码这种方式差不多那?
这就要介绍 MapStruct 这个工具类,这个工具类之所以运行速度与硬编码差不多,这是因为他在编译期间就生成了 Java Bean 属性复制的代码,运行期间就无需使用反射或者字节码技术,所以确保了高性能。
另外,由于编译期间就生成了代码,所以如果有任何问题,编译期间就可以提前暴露,这对于开发人员来讲就可以提前解决问题,而不用等到代码应用上线了,运行之后才发现错误。
下面我们来看下,怎么使用这个工具类,首先我们先引入这个依赖:
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.3.1.Final</version> </dependency>
其次,由于 MapStruct 需要在编译器期间生成代码,所以我们需要 maven-compiler-plugin
插件中配置:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.8.1</version> <configuration> <source>1.8</source> <!-- depending on your project --> <target>1.8</target> <!-- depending on your project --> <annotationProcessorPaths> <path> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.3.1.Final</version> </path> <!-- other annotation processors --> </annotationProcessorPaths> </configuration> </plugin>
接下来我们需要定义映射接口,代码如下:
@Mapper public interface StudentMapper { StudentMapper INSTANCE = Mappers.getMapper(StudentMapper.class); @Mapping(source = "no", target = "number") @Mapping(source = "createDate", target = "createDate", dateFormat = "yyyy-MM-dd") StudentDO dtoToDo(StudentDTO studentDTO); }
我们需要使用 MapStruct 注解 @Mapper
定义一个转换接口,这样定义之后,StudentMapper
的功能就与 BeanUtils 等工具类一样了。
其次,由于我们 DTO 与 DO 对象中存在字段名不一致的情况,所以我们还在在转换方法上使用 @Mapping
注解指定字段映射。另外我们 createDate
字段类型不一致,这里我们还需要指定时间格式化类型。
上面定义完成之后,我们就可以直接使用 StudentMapper
一行代码搞定对象转换。
// 忽略其他代码 StudentDO studentDO = StudentMapper.INSTANCE.dtoToDo(studentDTO);
如果我们对象使用 Lombok 的话,使用 @Mapping
指定不同字段名,编译期间可能会抛出如下的错误: