在实际的开发过程中,会出现一种开发的需求,就是进行bean的拷贝,最笨的方法就是通过get,set来处理。这种方法虽然简单明了,但是会增加很大的代码量。
很多组件提供了bean拷贝的工具
1. Bean Copy原理
对象拷贝技术模式一般分为:
-
注解处理器技术,是在编译期处理注解,生成代码,所以在运行时可以提供跟手写对象拷贝相同的性能。
-
字节码增强技术,是在运行时修改字节码,会损耗运行时性能,性能不如 APT ,但比反射性能好很多。
-
反射技术,会涉及到动态类型解析、参数拆箱装箱操作、方法可见性检查、参数校验等,所以运行时性能会比较差一些,JavaDoc 建议在性能比较敏感的系统中应该避免频繁使用反射。
Bean Copy使用的是“注解配置 + 注解处理器”的技术方案。(编译器执行)
目前,在使用的过程中,总结了以下bean拷贝的方式
① mapstruct的BeanCopyUtil
② apache的BeanUtils.copyProperties
③ orika-mapper 的映射copy
④ mdp的BeanCopyUtil
针对这四种bean拷贝,我这边将一一进行介绍
2 bean拷贝的方式
2.1 mapstruct的BeanCopyUtil
2.1.1 pom依赖引入
<!-- mapstruct -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
2.1.2 使用方法
① 创建两个对象,source和target
② 编写map映射接口
@Mapper
public interface TestCopyMapper {
Target copy(Source source);
}
③ 调用此方法之后会自动在target生成实现类
优点:
① 会生成实际的代码,可以debug,代码更透明
② 在编译期生存代码性能比较好
③ 可以指定字段的映射,还可以多个model映射到一个model中
缺点:
① 需要额外编写接口以及方法可维护性性低
示例:
@Mapper(imports = DateTool.class)
public interface BeanCopyUtil {
BeanCopyUtil INSTANCE = Mappers.getMapper(BeanCopyUtil.class);
@Mapping(source = "name", target = "bName")
BTest copy(ATest aTest);
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
public class ATest {
private String name;
private int age;
private List<String> arrayList;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder(toBuilder = true)
public class BTest {
private String bName;
private int age;
private List<String> arrayList;
}
// 使用方法
@Test
public void testCopy() {
ATest aTest = new ATest();
aTest.setAge(10);
aTest.setArrayList(Lists.newArrayList("111", "222"));
BTest bTest = BeanCopyUtil.INSTANCE.copy(aTest);
System.out.println(bTest);
}
2.2 apache的BeanUtils.copyProperties
这种方式是Spring提供的bean拷贝的方式里面的坑很多,
如果source里面的字段是包装类型的值,target对应的字段是基本类型,包装类型的值是null,会导致空指针异常,bean拷贝没有办法进行深层次的拷贝,比如里面有List
2.3 orika-mapper
使用步骤:
1、引入POM文件
<dependency>
<groupId>ma.glasnost.orika</groupId>
<artifactId>orika-core</artifactId>
<version>1.5.4</version>
</dependency>
2、工具类以及测试方法
package com.meituan.grocery.sce.delivery.service.utils;
import com.meituan.grocery.sce.delivery.service.bean.ABean;
import com.meituan.grocery.sce.delivery.service.bean.BBean;
import ma.glasnost.orika.MapperFacade;
import ma.glasnost.orika.MapperFactory;
import ma.glasnost.orika.impl.DefaultMapperFactory;
import java.util.ArrayList;
import java.util.List;
public class OrikaUtils {
/**
* * 构造一个MapperFactory
*/
private final static MapperFactory DEFAULT_MAPPER_FACTORY = new DefaultMapperFactory.Builder().build();
/**
* 获得映射MapperFacade
*/
private final static MapperFacade MAPPER = DEFAULT_MAPPER_FACTORY.getMapperFacade();
public static <S, T> T copyBean(S source, Class<T> target) {
return MAPPER.map(source, target);
}
public static <S, T> List<T> copyList(Iterable<S> source, Class<T> targetClass) {
if (source == null) {
return new ArrayList<>();
}
return MAPPER.mapAsList(source, targetClass);
}
public static void main(String[] args) {
ABean originBean = new ABean("aName", 10, 10);
BBean bBean = OrikaUtils.copyBean(originBean, BBean.class);
}
}
优点:
可以映射复杂和深层结构的对象通过将嵌套属性映射到顶级属性来“展平”或“展开”对象,反之亦然动态创建映射程序,并应用自定义设置来控制部分或全部映射创建转换器以完全控制对象图中任意位置特定对象集的映射(按类型,甚至按特定属性名)处理代理或增强对象(如Hibernate或各种模拟框架的代理或增强对象),一个配置中应用双向映射映射到目标抽象类或接口的适当具体类的实例。
缺点
运行时生成映射类,虽然会缓存,但第一次使用的时候会慢点
总结:个人看法
性能方面 mapstruct>orika-mapper>beanutil
使用便捷性 beanutil=orika-mapper>mapstruct
功能 orika-mapper>mdp>mapstuct=beanutil
评论区