Java對象的拷貝和轉換,BeanUtils拷貝和 cglib拷貝


前言

因為工作中會不可避免的使用model的值拷貝,或者DTO轉實體類,或者屬性值特別多的部分拷貝。還有集合的泛型類型的轉換,所以利用spring的BeanUtils和cglib寫了簡單的工具類來供大家參考,另外注意一點就是Apache也有提供BeanUtils,但是由於我不知道的某某原因存在性能較差。當然最快的還是cglib,這不是本文所關注的重點,本文是工作中奔着實用和學習的目標去的。
測試類中也對分組進行了 java8的寫法進行對比,可以對比學習一下

當然啦還有性能對比
幾種copyProperties工具類性能比較:https://www.jianshu.com/p/bcbacab3b89e

CGLIB中BeanCopier源碼實現:https://www.jianshu.com/p/f8b892e08d26

Java Bean Copy框架性能對比:https://yq.aliyun.com/articles/392185

正文

工具類涉及到這些jar包,

spring的 spring-beans-xxxx.RELEASE.jar
cglib的 cglib-2.2.jar
阿里巴巴的json工具 fastjson-1.2.31.jar

代碼和測試類

功能包含
對象的屬性的拷貝(如果是復雜類型,如屬性為另一個class類,請自行進行修改進行迭代拷貝,不然該屬性值會為null,同理以下也是如此,切記切記)
屬性的部分拷貝,如幾十上百個以上的屬性只拷貝其中的百分之六七十,一個個進行set很麻煩,可以使用部分拷貝
集合的類型轉換, 如 List 轉換為 List 其中A、B均為 model 形式的,也就是說有getter和setter,其中要是轉換成為 Map 可以使用 fastjson 轉換。
List分組 java8 的寫法更好。

import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.Consumer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;

import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.beans.BeanMap;

/** <p>Title: TemplateCodeUtil</p>  
 * <p>Description: 對象拷貝與轉化 </p>  
 * @author houzw 
 * @date 2019年4月18日  
 */  
public class TemplateCodeUtil {

    protected static final Logger logger = LoggerFactory.getLogger(TemplateCodeUtil.class);

    // 使用多線程安全的Map來緩存BeanCopier,由於讀操作遠大於寫,所以性能影響可以忽略
    public static ConcurrentHashMap<String, BeanCopier> beanCopierMap = new ConcurrentHashMap<String, BeanCopier>();
    
    /**
     * 通過cglib BeanCopier形式,使用cglib拷貝,同屬性同類型拷貝
     * 
     * @param source 源轉換的對象
     * @param target 目標轉換的對象
     */
    public static void copyProperties(Object source, Object target) {
        String beanKey = generateKey(source.getClass(), target.getClass());
        BeanCopier copier = null;
        if (beanCopierMap.containsKey(beanKey)) {
            copier = beanCopierMap.get(beanKey);
        } else {
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            beanCopierMap.putIfAbsent(beanKey, copier);// putIfAbsent已經實現原子操作了。
        }
        copier.copy(source, target, null);
    }
    
    /**
     * 通過cglib BeanCopier形式,使用cglib拷貝,同屬性同類型拷貝
     *
     * @param source 源轉換的對象
     * @param target 目標轉換的對象
     * @param action 支持Lambda函數給拷貝完的對象一些自定義操作
     * @return
     */
    public static <O,T> T copyProperties(O source, T target, Consumer<T> action) {
        String beanKey = generateKey(source.getClass(), target.getClass());
        BeanCopier copier = null;
        if (beanCopierMap.containsKey(beanKey)) {
            copier = beanCopierMap.get(beanKey);
        } else {
            copier = BeanCopier.create(source.getClass(), target.getClass(), false);
            beanCopierMap.putIfAbsent(beanKey, copier);// putIfAbsent已經實現原子操作了。
        }
        copier.copy(source, target, null);
        action.accept(target);
        return target;
    }

    private static String generateKey(Class<?> class1, Class<?> class2) {
        return class1.toString() + class2.toString();
    }

    /**
     * transalte常規反射拷貝,同名同類型拷貝,推薦使用  transTo(O o, Class<T> clazz) 
     * ×通過常規反射形式 DTO對象轉換為實體對象。如命名不規范或其他原因導致失敗。
     * @param t 源轉換的對象
     * @param e 目標轉換的對象
     * 
     */
    public static <T, O> void transalte(T t, O e) {
        Method[] tms = t.getClass().getDeclaredMethods();
        Method[] tes = e.getClass().getDeclaredMethods();
        for (Method m1 : tms) {
            if (m1.getName().startsWith("get")) {
                String mNameSubfix = m1.getName().substring(3);
                String forName = "set" + mNameSubfix;
                for (Method m2 : tes) {
                    if (m2.getName().equals(forName)) {
                        // 如果類型一致,或者m2的參數類型是m1的返回類型的父類或接口
                        boolean canContinue = m2.getParameterTypes()[0].isAssignableFrom(m1.getReturnType());
                        if (canContinue) {
                            try {
                                m2.invoke(e, m1.invoke(t));
                                break;
                            } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
                                // log未配置會報 WARN No appenders could be found for
                                // logger log4j:WARN Please initialize the log4j
                                // system properly.
                                logger.debug("DTO 2 Entity轉換失敗");
                                e1.printStackTrace();
                            }
                        }
                    }
                }
            }

        }
        // logger.debug("轉換完成");

    }

    /**
     * 對象屬性拷貝,忽略源轉換對象的 null值
     * @param t 源轉換的對象
     * @param e 目標轉換的對象
     * 
     */
    public static <T, O> void copyPropertiesIgnoreNull(T t, O e) {
        final BeanWrapper bw = new BeanWrapperImpl(t);
        PropertyDescriptor[] pds = bw.getPropertyDescriptors();
        
        Set<String> emptyNames = new HashSet<String>();
        for(PropertyDescriptor pd : pds) {
            Object srcValue = bw.getPropertyValue(pd.getName());
            if (srcValue != null) emptyNames.add(pd.getName());
        }
        partialCopy(t, e, emptyNames.toArray());
    }

    /**
     * 對象屬性拷貝,同屬性同類型拷貝,忽略源轉換對象不符合自定義規則的屬性
     * @param t 源轉換的對象
     * @param e 目標轉換的對象
     * @param action  lambda傳入的是t的屬性名和屬性值,返回true和e對象有該屬性則拷貝該值
     */
    public static <T, O> void copyPropertiesIgnoreCustom(T t, O e, BiPredicate<String, Object> action) {
        final BeanWrapper bw = new BeanWrapperImpl(t);
        PropertyDescriptor[] pds = bw.getPropertyDescriptors();
        
        Set<String> emptyNames = new HashSet<String>();
        for(PropertyDescriptor pd : pds) {
            Object srcValue = bw.getPropertyValue(pd.getName());
            // 自定義條件的成立與否,返回true則拷貝,反之不拷貝,滿足同屬性同類型。
            if (action.test(pd.getName(), srcValue)) emptyNames.add(pd.getName());
        }
        partialCopy(t, e, emptyNames.toArray());
    }


    /** 同類型字段部分拷貝
     * @param t 源數據對象
     * @param e 接收對象
     * @param key 要拷貝的字段名數組
     */
    public static <T> void partialCopy(T t , T e, Object... key) {
        
        BeanMap t1 = BeanMap.create(t);
        BeanMap e1 = BeanMap.create(e);
        int i = key.length;
        while (i-- > 0) {
            e1.replace(key[i], t1.get(key[i]));
        }
    }


    /**
     * 對象集合轉換,兩個對象的屬性名字需要一樣
     */
    public static <T, O> List<T> transTo(List<O> fromList, Class<T> clazz) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        List<T> toList = new ArrayList<>();
        for (O e : fromList) {
            T entity = clazz.newInstance();
            BeanUtils.copyProperties(e, entity);
            toList.add(entity);
        }
        return toList;
    }


    /**
     * 對象集合轉換,兩個對象的屬性名字需要一樣,並可自定義設置一些參數
     */
    public static <T, O> List<T> transTo(List<O> fromList, Class<T> clazz, OnTransListener<T, O> onTransListener) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        List<T> toList = new ArrayList<>();
        for (O e : fromList) {
            T entity = clazz.newInstance();
            BeanUtils.copyProperties(e, entity);
            if (onTransListener != null) {
                onTransListener.doSomeThing(entity, e);
            }
            toList.add(entity);
        }
        return toList;
    }
    
    /**
     * 使用BeanUtils,對象集合轉換,兩個對象的屬性名字需要一樣,並可自定義設置一些參數
     * 
     * @param fromList 源數據List
     * @param clazz 需要轉換成的clazz
     * @param action 支持lambda表達式自定義設置一些參數
     * @return
     */
    public static <T, O> List<T> transToCustom(List<O> fromList, Class<T> clazz, BiConsumer<O, T> action) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        List<T> toList = new ArrayList<>();
        for (O e : fromList) {
            T entity = clazz.newInstance();
            BeanUtils.copyProperties(e, entity);
            action.accept(e, entity);
            toList.add(entity);
        }
        return toList;
    }

    /**
     * 使用BeanUtils,對象轉換,E轉為t對象
     */
    public static <T, O> T transTo(O e, Class<T> clazz) throws IllegalAccessException, InstantiationException, InvocationTargetException {
        T t = clazz.newInstance();
        BeanUtils.copyProperties(e, t);
        return t;
    }

    /**
     * 接口常量轉為指定類型的List
     */
    public static <T> List<T> interfaceTransToVal(Class<?> clazz, Class<T> toClazz) throws IllegalAccessException {
        List<T> list = new ArrayList<>();
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            T t = (T) field.get(clazz);
            if (t.getClass() == toClazz) {
                list.add(t);
            }
        }
        return list;
    }

    /**
     * json 轉為對象
     */
    public static <T> T jsonToObject(String jsonStr, Class<T> clazz) {
//        ObjectMapper objectMapper = new ObjectMapper();
        T t = null;
        try {
            t = JSONObject.parseObject(jsonStr, clazz);
//            t = objectMapper.readValue(jsonByte, clazz);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return t;
    }
    
    /** 
     * json 轉為Array
     */
    public static <T> List<T> jsonToArray(String jsonStr, Class<T> clazz) {
        if (jsonStr == null || jsonStr.length() == 0) {
            return null;
        } else {
//        ObjectMapper objectMapper = new ObjectMapper();
            List<T> arr = null;
            try {
                arr = JSON.parseArray(jsonStr, clazz);
//                JavaType javaType = objectMapper.getTypeFactory().constructParametricType(ArrayList.class, clazz);  
//                arr = objectMapper.readValue(jsonStr, javaType);
            } catch (JSONException e) {
                e.printStackTrace();
            }
            return arr;
        }
    }

    /**
     * Map 轉為對象,字段格式要一致
     */
    public static <T> T mapTrasnToObject(Map<String, Object> map, Class<T> clazz) throws IOException {
        byte[] jsonBytes = JSON.toJSONBytes(map, SerializerFeature.WriteNullStringAsEmpty);
        T t = JSON.parseObject(jsonBytes, clazz,Feature.IgnoreNotMatch);
//        ObjectMapper objectMapper = new ObjectMapper();
//        String jsonStr = objectMapper.writeValueAsString(map);
//        T t = objectMapper.readValue(jsonStr, clazz);
        return t;
    }
    
    /**
     * object(非基本類型和List類型)轉為 Map
     * 若類型不正確返回的map為EmptyMap,其不可添加元素
     * @param obj
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Map<String, Object> objectTrasnToMap(Object obj) {
        if (Objects.isNull(obj)) {
            return Collections.EMPTY_MAP;
        } else {
            Map<String, Object> map = BeanMap.create(obj);
            return map.size() == 1 && map.containsKey("empty") ? Collections.EMPTY_MAP : new HashMap<>(map);
        }
    }

    /**
     * 集合根據某個關鍵字進行分組
     */
    public static <T> Map<String, List<T>> groupBy(List<T> tList, StringKey<T> stringKey) {
        Map<String, List<T>> map = new HashMap<>();
        for (T t : tList) {
            if (map.containsKey(stringKey.key(t))) {
                map.get(stringKey.key(t)).add(t);

            } else {
                List<T> list = new ArrayList<>();
                list.add(t);
                map.put(stringKey.key(t), list);
            }
        }
        return map;
    }
    


    /**
     * 把對象轉為json,並輸出到日志中
     */
    public static void logObject(String tag, Object object) {
        try { // 保留空值數據為 ""
            String json = JSON.toJSONString(object, SerializerFeature.WriteNullStringAsEmpty);
//            ObjectMapper objectMapper = new ObjectMapper();
//            String json = objectMapper.writeValueAsString(object);
            logger.info("{}{}", tag, json);
        } catch (JSONException e) {
            e.printStackTrace();
            logger.info("exception = {}", e);
        }
    }


    /**
     * 集合分組的關鍵詞
     */
    public interface StringKey<T> {
        String key(T t);
    }


    /**
     * 編寫一些額外邏輯
     */
    public interface OnTransListener<T, O> {
        void doSomeThing(T t, O e);
    }


}

測試類需要說明一下的就是DTO的屬性比實體類屬性要多一點,可以自己創建修改一下。因為懶所以直接以輸出的形式輸出結果,並未使用Assert,請各位有強迫X的請不要介意,謝謝

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.alibaba.fastjson.JSON;
import com.neusoft.util.TemplateCodeUtil.OnTransListener;
import com.neusoft.util.TemplateCodeUtil.StringKey;

import kjcxpt.kjcxpthd.dto.PrincipalConditionAndMemberDTO;
import kjcxpt.kjcxpthd.entity.PrincipalCondition;

/** <p>Title: TemplateCodeUtilTest</p>  
 * <p>Description: 該方法為測試方法,在方法名上直接右鍵選擇juni運行</p>  
 * @author houzw
 * @date 2019年5月6日  
 */  
public class TemplateCodeUtilTest {

    // 當前DTO屬性多
    private  PrincipalConditionAndMemberDTO dto = new PrincipalConditionAndMemberDTO();
    // 當前entity屬性少
    private  PrincipalCondition entity = new PrincipalCondition();
    
    private  List<PrincipalConditionAndMemberDTO> dtoList = null;
    private  List<PrincipalCondition> entityList = null;
    
    @Before
    public void setUpBefore() throws Exception {
        dto.setIdCard("sds");  // entity中沒有該屬性
        dto.setDr(111); // entity中有該屬性
        entity.setUnitName("dto的值會覆蓋此值");
        
        dtoList = new ArrayList<PrincipalConditionAndMemberDTO>();
        PrincipalConditionAndMemberDTO dtoadd = null;
        int num = 0;
        do {
            dtoadd = new PrincipalConditionAndMemberDTO();
            dtoadd.setIdCard("testList"+ num);
            dtoadd.setDr(Integer.valueOf(num));
            dtoList.add(dtoadd);
            num++;
        } while (dtoList.size() < 10);
        
        entityList = new ArrayList<PrincipalCondition>();
        PrincipalCondition entityadd = null;
        do {
            entityadd = new PrincipalCondition();
            entityList.add(entityadd);
        } while (entityList.size() < 10);
    }

    @After
    public void tearDownAfter() throws Exception {
        System.out.println("dto:" + JSON.toJSONString(dto));
        System.out.println("entity:" + JSON.toJSONString(entity));
        System.out.println("dtoList:" + JSON.toJSONString(dtoList));
        System.out.println("entityList:" + JSON.toJSONString(entityList));
    }

    @Test
    public void testCopyPropertiesObjectObject() {
        TemplateCodeUtil.copyProperties(dto, entity);
    }

    @Test
    public void testCopyPropertiesOTConsumerOfT() {
        // 對拷貝后的對象操作,使用lambda表達式
        PrincipalCondition copyProperties = TemplateCodeUtil.copyProperties(dto, entity, t -> System.out.println(t.getDr()));
        System.out.println(copyProperties.getDr());
        
        // 設置其他屬性
        TemplateCodeUtil.copyProperties(dto, entity, t -> {
            t.setFoundYear("2019");
            t.setFinishTime(new Date());
        });
    }

    @Test
    public void testTransalte() {
        // 常規的利用反射的 拷貝
        TemplateCodeUtil.transalte(dto, entity);
    }

    @Test
    public void testCopyPropertiesIgnoreNull() {
        TemplateCodeUtil.copyPropertiesIgnoreNull(dto, entity);
        System.out.println(entity.getDr());
        dto.setDr(null);
        entity.setDr(111111);
        TemplateCodeUtil.copyPropertiesIgnoreNull(dto, entity);
    }

    @Test
    public void testCopyPropertiesIgnoreCustom() {
        // lambda自定義 那些需要賦值,前提必須是  同名同屬性,否則 返回true 也不拷貝
        TemplateCodeUtil.copyPropertiesIgnoreCustom(dto, entity, (key, value) -> {
            if ("dr".equals(key) && value != null) {
                value = "賦值給源數據value,這該如何解釋"; // 這種操作我無法解釋  ^_^
                return true;
            }
            if( "idCard".equals(key)) { // 即使返回true,entity沒有該屬性,所以也不會有該屬性和值
                return true;
            }
            if (value != null) { // 不為null 賦值
                return true;
            }else {
                return false;
            }
        });
    }

    @Test
    public void testPartialCopy() {
        String[] names = new String[]{"dr"};
        
        TemplateCodeUtil.partialCopy(dto, entity, names);
        // 此時unitName還未被覆蓋,只拷貝 dr屬性
        System.out.println(entity.getUnitName());
        
        // 測試部分拷貝,拷貝dr 和 unitName
        TemplateCodeUtil.partialCopy(dto, entity, "dr","unitName");
    }

    @Test
    public void testTransToListOfEClassOfT() throws Exception {
        // dto轉換為entity,使用的是 beanUnits
        List<PrincipalCondition> transTo = TemplateCodeUtil.transTo(dtoList, PrincipalCondition.class);
        System.out.println(JSON.toJSONString(transTo));
    }

    @Test
    public void testTransToListOfEClassOfTOnTransListenerOfTE() {
        // 自定義的拷貝后的轉換方法
        class myOnTransListener implements OnTransListener<PrincipalCondition, PrincipalConditionAndMemberDTO> {

            @Override
            public void doSomeThing(PrincipalCondition en, PrincipalConditionAndMemberDTO dt) {
                // 這明顯和lambda的簡潔度比起來有點繁雜  transToCustom
                System.out.println(dt.getIdCard());
                en.setDescription("ssss");
            }

        }
        try {
            entityList = TemplateCodeUtil.transTo(dtoList, PrincipalCondition.class, new myOnTransListener());
        } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testTransToCustomListOfEClassOfTBiConsumerOfTE() throws IllegalAccessException, InstantiationException, InvocationTargetException {
        entityList = TemplateCodeUtil.transToCustom(dtoList, PrincipalCondition.class, (d, e)-> {
            System.out.println(d.getIdCard());
            e.setDescription("ssss");
        });
    }

    @Test
    public void testTransToEClassOfT() throws IllegalAccessException, InstantiationException, InvocationTargetException {
        entityList = TemplateCodeUtil.transTo(dtoList, PrincipalCondition.class);
    }

    @Test
    public void testInterfaceTransToVal() throws IllegalAccessException {
        // 接口常量這種是 反模式,用接口存常量是一種不良的習慣。所以我就懶得做示范了。沒什么必要
    }

    @Test
    public void testJsonToObject() {
        
    }

    @Test
    public void testJsonToArray() {
        fail("Not yet implemented");
    }

    @Test
    public void testMapTrasnToObject() {
        fail("Not yet implemented");
    }

    @Test
    public void testGroupBy() {
        class myStringKey implements StringKey<PrincipalConditionAndMemberDTO> {

            @Override
            public String key(PrincipalConditionAndMemberDTO t) {
                // 選取dto的一個字段 idCard 
                return t.getIdCard();
            }
            
        }
        Map<String, List<PrincipalConditionAndMemberDTO>> groupBy = TemplateCodeUtil.groupBy(dtoList, new myStringKey());
        System.out.println(JSON.toJSONString(groupBy));
        
        Map<Integer, List<PrincipalConditionAndMemberDTO>> collect = dtoList.stream().collect(Collectors.groupingBy(PrincipalConditionAndMemberDTO::getDr));
        System.out.println(JSON.toJSONString(collect));
    }

    @Test
    public void testLogObject() {
        // 這個是在web環境下有slf4j的配置才行,生產環境請刪除該輸出語句
    }

}

結束祝語

以上就這么多,祝各位玩的愉快!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM