一、Beancopier原理
Beancopier是一个Java实现的Bean拷贝工具,其原理是通过反射机制实现对Bean的属性值的读取和赋值。
它核心类是BeanCopier,通过源对象和目标对象的Class对象建立出一个BeanCopier实例,在调用copy方法时,实现对源对象属性的拷贝到目标对象上。
public class BeanCopier {
private static final Map BEAN_COPIERS = new ConcurrentHashMap();
public static void copy(Object source, Object target) {
String key = genKey(source.getClass(), target.getClass());
BeanCopier copier = BEAN_COPIERS.get(key);
if (copier == null) {
copier = BeanCopier.create(source.getClass(), target.getClass(), false);
BEAN_COPIERS.put(key, copier);
}
copier.copy(source, target, null);
}
}
可以看到,该实现方式对比其他Bean拷贝方式,具有很高的性能优势,而且具有简洁的API接口。
二、Beancopier使用converter
有时候在进行Bean的拷贝的时候,源对象的属性与目标对象的属性名称和类型存在差异,因此需要进行相应的类型转换,这时候就需要使用到Beancopier提供的Converter接口。
public interface Converter {
Object convert(Object source, Class> sourceClazz, Class> targetClazz);
}
Converter接口仅有一个方法,用于实现源对象属性到目标对象属性的类型转换,我们可以在创建BeanCopier对象的时候传入一个Converter实例。
public class PersonConvert implements Converter {
@Override
public Object convert(Object source, Class> sourceClazz, Class> targetClazz) {
if (source instanceof String && targetClazz == LocalTime.class) {
return LocalTime.parse((String) source, DateTimeFormatter.ISO_LOCAL_TIME);
}
return source;
}
}
public class Person {
private String name;
private int age;
private String gender;
private LocalTime createTime;
// getter and Setter
}
public class PersonDTO {
private String fullName;
private int yearsOld;
private String sex;
private LocalDateTime createTime;
// getter and Setter
}
public class BeancopierConverterTest {
@Test
public void testBeancopierWithConverter() {
Person person = new Person();
person.setName("Shack");
person.setAge(34);
person.setGender("male");
person.setCreateTime(LocalTime.parse("03:18:37", DateTimeFormatter.ISO_LOCAL_TIME));
PersonDTO personDTO = new PersonDTO();
BeanCopier copier = BeanCopier.create(Person.class, PersonDTO.class, true);
copier.copy(person, personDTO, new PersonConvert());
assertThat(personDTO.getFullName()).isEqualTo("Shack");
assertThat(personDTO.getYearsOld()).isEqualTo(34);
assertThat(personDTO.getSex()).isEqualTo("male");
assertThat(personDTO.getCreateTime()).isEqualTo(LocalDateTime.now().withNano(0));
}
}
我们定义一个PersonConverter类实现Converter接口,实现将Person中的LocalTime类型属性值转换到PersonDTO的LocalDateTime类型属性上。
在调用BeanCopier的copy方法时,将PersonConvert实例传入即可实现局部的类型转换。
三、Beancopier反射性能
既然Beancopier是基于反射机制实现的,那么其性能是否优于其他的Bean拷贝机制呢?
考虑到Java编译器的优化机制,在源对象和目标对象属性名称相同、属性类型相同的情况下,使用CGLib实现的BeanCopier性能是最好的。
但是,当属性名称和属性类型存在差异时,CGLib实现的BeanCopier会退化成暴力反射,因此性能会比较低下。而基于ASM实现的BeanCopier相对于CGLib则会更高效,但是其实现相对于CGLib复杂度会高些。
不过作为一个优秀的工具类,Beancopier并不是只能单独使用。它也经常与其他框架、工具类一起使用(比如在Dubbo中的应用)。并且,Beancopier支持多线程操作,具有很好的线程安全性。
public class BeancopierPerformanceTest {
private static final int COUNT = 100 * 10000;
private static CglibBeanCopier cglibBeanCopier = CglibBeanCopier.create(Source.class, Target.class, true);
private static AsmBeanCopier asmBeanCopier = AsmBeanCopier.create(Source.class, Target.class, true);
private static BeanCopier beanCopier = BeanCopier.create(Source.class, Target.class, true);
private static ObjectMapper mapper = new ObjectMapper();
@Test
public void testPerformance() {
Source sourceObject = getSourceObject();
Stopwatch cglibWatch = Stopwatch.createStarted();
for (int i = 0; i < COUNT; i++) {
Target targetObject = new Target();
cglibBeanCopier.copy(sourceObject, targetObject, null);
}
assertThat(cglibWatch.elapsed().toMillis()).isLessThan(1000);
Stopwatch asmWatch = Stopwatch.createStarted();
for (int i = 0; i < COUNT; i++) {
Target targetObject = new Target();
asmBeanCopier.copy(sourceObject, targetObject, null);
}
assertThat(asmWatch.elapsed().toMillis()).isLessThan(1000);
Stopwatch watch = Stopwatch.createStarted();
for (int i = 0; i < COUNT; i++) {
Target targetObject = new Target();
beanCopier.copy(sourceObject, targetObject, null);
}
assertThat(watch.elapsed().toMillis()).isLessThan(1000);
}
private Source getSourceObject() {
Map sourceMap = Maps.newHashMap();
sourceMap.put("id", 1);
sourceMap.put("name", "John");
sourceMap.put("age", 20);
sourceMap.put("isDeleted", true);
sourceMap.put("createTime", System.currentTimeMillis());
return mapper.convertValue(sourceMap, Source.class);
}
static class Source {
private int id;
private String name;
private int age;
private boolean isDeleted;
private long createTime;
// getter and Setter
}
static class Target {
private int id;
private String name;
private int age;
private boolean isDeleted;
private long createTime;
// getter and Setter
}
}
我们可以通过一个简单的压力测试来测试各种BeanCopier实现方式在拷贝大量数据时的性能表现。
结果显示Beancopier的性能与其他实现方式相比不相上下,反应较快。
四、Beancopier是线程安全的吗
Beancopier实现是线程安全的。由于其内部实现使用Map缓存了已经创建的BeanCopier对象,以达到高效的目的,但是在并发场景下,由于ConcurrentHashMap并不能完全规避并发产生的问题。因此,如果使用beancopier在多线程场景下频繁获取BeanCopier的实例,可能会存在一定的线程安全问题。
五、总结
Beancopier是一款非常值得推荐的Bean拷贝工具,其性能和使用上都很优秀。
如果需要实现Bean属性间的快速拷贝,而且Bean属性名称和类型没有出现差异,我们可以尝试使用CGLib实现的BeanCopier,可以获得最好的性能。
如果在Bean属性名称和类型存在差异时,我们可以尝试使用Beancopier实现局部的类型转换,不仅方便快捷,而且让代码更加简洁明了。