大家在做java開發時,肯定會遇到api層參數對象傳遞給服務層,
或者把service層的對象傳遞給dao層,他們之間又不是同一個類型對象,
但字段又是一樣,如果還是用普通的get、set方式來處理話,比較繁瑣,.... 那么你來跟我學....."天下武功,唯快不破"===>要的就是快....
1.使用get/set不用說了,字段一多腦殼大....
2.BeanCopier 使用了cglib的修改字節碼,真的是動態Read Writer getter/setter 快的一逼啊 源碼過於簡單 直接上代碼 我使用的最基本的,大家可以自定義Converter,定以后完全按照Converter轉換
需要注意的是:
* 1.當源類和目標類的屬性名稱、類型都相同,拷貝結果棒棒噠。
* 2.當源對象和目標對象的屬性名稱相同、類型不同,那么名稱相同而類型不同的屬性不會被拷貝。另外注意,原始類型(int,short,char)和 他們的包裝類型,在這里都被當成了不同類型。因此不會被拷貝。
* 3.源類或目標類的setter比getter少,拷貝沒問題,此時setter多余,但是不會報錯。
* 4.源類和目標類有相同的屬性(兩者的getter都存在),但是目標類的setter不存在, 此時會拋出NullPointerException(這個在高版本bug已經修改測試通過,我使用的49.0)
1 /**
2 * @author LiJing
3 * @ClassName: BeanCopyUtils
4 * @Description: 基於BeanCopier的屬性拷貝
5 * @date 2019/4/17 9:1521 * * 凡是和反射相關的操作,基本都是低性能的。凡是和字節碼相關的操作,基本都是高性能的。
22 * <p/>
23 */
24 @Slf4j
25 public class BeanCopyUtils {
26
27 /**
28 * 創建過的BeanCopier實例放到緩存中,下次可以直接獲取,提升性能
29 */
30 private static final Map<String, BeanCopier> BEAN_COPIERS = new ConcurrentHashMap<>();
31
32 /**
33 * 該方法沒有自定義Converter,簡單進行常規屬性拷貝
34 *
35 * @param srcObj 源對象
36 * @param destObj 目標對象
37 */
38 public static void copy(final @NotNull Object srcObj, final @NotNull Object destObj) {
39 String key = genKey(srcObj.getClass(), destObj.getClass());
40 BeanCopier copier;
41 if (!BEAN_COPIERS.containsKey(key)) {
42 copier = BeanCopier.create(srcObj.getClass(), destObj.getClass(), false);
43 BEAN_COPIERS.put(key, copier);
44 } else {
45 copier = BEAN_COPIERS.get(key);
46 }
47 copier.copy(srcObj, destObj, null);
48 }
49
50 private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
51 return srcClazz.getName() + destClazz.getName();
52 }
53 }
3.接下來再來玩一個org.springframework.beans.BeanUtils,這個比較騷,使用反射,反射就比較慢了,要加載字節碼,反編譯,再實例化,再映射屬性.....來來上二斤代碼,先吃着 有用的地方不多大家看着用哈<copyProperties>其實有這個方法就夠了
1 import lombok.extern.slf4j.Slf4j;
2 import org.springframework.beans.BeanUtils;
3 import org.springframework.beans.BeansException;
4 import org.springframework.beans.FatalBeanException;
5 import org.springframework.util.Assert;
6 import org.springframework.util.ClassUtils;
7 17
18 /**
19 * @author LiJing
20 * @ClassName: BeanUtils
21 * @Description: 屬性拷貝工具類
22 * @date 2018/4/10 11:54
23 */
24 @Slf4j
25 public class MyBeanUtils extends BeanUtils {
26
27 /**
28 * 從org.springframework.beans.BeanUtils類中直接復制過來
29 *
30 * @param source
31 * @param target
32 * @throws BeansException
33 */
34 public static void copyProperties(Object source, Object target) throws BeansException {
35 copyProperties(source, target, null, (String[]) null);
36 }
37
38 /**
39 * 從org.springframework.beans.BeanUtils類中直接復制過來,修改部分代碼,不為null才復制
40 *
41 * @param source
42 * @param target
43 * @param editable
44 * @param ignoreProperties
45 * @throws BeansException
46 */
47 private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties)
48 throws BeansException {
49
50 Assert.notNull(source, "Source must not be null");
51 Assert.notNull(target, "Target must not be null");
52
53 Class<?> actualEditable = target.getClass();
54 if (editable != null) {
55 if (!editable.isInstance(target)) {
56 throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
57 "] not assignable to Editable class [" + editable.getName() + "]");
58 }
59 actualEditable = editable;
60 }
61 PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
62 List<String> ignoreList = (ignoreProperties != null) ? Arrays.asList(ignoreProperties) : null;
63
64 for (PropertyDescriptor targetPd : targetPds) {
65 Method writeMethod = targetPd.getWriteMethod();
66 if (writeMethod != null && (ignoreProperties == null || (!ignoreList.contains(targetPd.getName())))) {
67 PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
68 if (sourcePd != null) {
69 Method readMethod = sourcePd.getReadMethod();
70 if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
71 try {
72 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
73 readMethod.setAccessible(true);
74 }
75 Object value = readMethod.invoke(source);
76 // 判斷被復制的屬性是否為null, 如果不為null才復制
77 if (value != null) {
78 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
79 writeMethod.setAccessible(true);
80 }
81 writeMethod.invoke(target, value);
82 }
83 } catch (Throwable ex) {
84 throw new FatalBeanException(
85 "不能拷貝屬性 '" + targetPd.getName() + "' 從原對象給目標對象,詳細原因見:", ex);
86 }
87 }
88 }
89 }
90 }
91 }
92
93
94 /**
95 * 構造函數.
96 */
97 public MyBeanUtils() {
98 throw new RuntimeException("this is a util class,can not instance");
99 }
411 }
4.還有很多屬性拷貝的方法例如org.apache.commons.beanutils.BeanUtils的copyProperties(Object dest, Object src)這里不舉例了 類似於Spring的屬性拷貝
5.還有使用easymapper來進行對象映射,但在項目中存在不穩性,偶爾出現映射不上的問題,會報異常com.baidu.unbiz.easymapper.exception.MappingException,大家可自行研究
其實有很多種方法進行屬性拷貝的,例如dozer等等 下面看下測試性能吧:以:萬級進行測試,我覺得Cglib太給力了.可以在遇到屬性拷貝瓶頸時考慮.當然他們各有優點哈,功能也不盡相同.還需要多使用體會.
輸出結果:手動Copy > cglibCopy > springBeanUtils > apachePropertyUtils > apacheBeanUtils 可以理解為: 手工復制 > cglib > 反射 > Dozer。
| 類型Framework |
測試性能(10000調用次)time |
| Pure get/set |
10ms |
| Easy mapper |
46ms |
| Cglib Beancopier |
11ms |
| Spring BeanUtils |
94ms |
| Apache BeanUtils |
249ms |
| Apache PropertieyUtils |
130ms |
| Dozer |
770ms |