一、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實現局部的類型轉換,不僅方便快捷,而且讓代碼更加簡潔明了。
原創文章,作者:HSEM,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/146153.html
微信掃一掃
支付寶掃一掃