動態地給Java對象添加字段並賦值


一、場景

  1. 需求的敘述比較抽象難懂,總之,最后想要的結果就是動態的給對象添加屬性,然后返回給前台。

二、思路

  1. 搜了一圈,還真有,基於cglib、commons-beanutils庫實現
  • 將原對象和擴展字段封裝為字段map
  • 基於字段map和原對象創建其子類對象
  • 重新將原字段值和擴展字段值賦給子類對象
  • 返回子類對象

三、實現

  1. maven依賴
(必須顯式添加)
<dependency>
 <groupId>commons-beanutils</groupId>
 <artifactId>commons-beanutils</artifactId>
 <version>1.9.3</version>
</dependency>
(用spring的間接依賴也可以,不必顯式添加)
<dependency>
 <groupId>cglib</groupId>
 <artifactId>cglib-nodep</artifactId>
 <version>3.2.4</version>
</dependency>
  1. 代碼實現
    為了能集中管理,我將所有涉及的類都寫在了同一個源文件中,如需測試,可以將下面代碼整體拷入一個Java類,解決好依賴,直接執行main函數
package com.yang.jmh.batch;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.cglib.beans.BeanMap;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Yang
 * @date: 2020/11/4 00:12
 * @description:
 */
public class Kill {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException, InvocationTargetException, IllegalAccessException {

        User user = new User();
        user.setName("Daisy");
        System.out.println("User:" + MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(user));

        Map<String, Object> propertiesMap = new HashMap<>(1);
        propertiesMap.put("age", 18);

        Object obj = ReflectUtil.getObject(user, propertiesMap);
        System.err.println("動態為User添加age之后,User:" + MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj));
    }
}

class DynamicBean {

    private Object target;

    private BeanMap beanMap;

    public DynamicBean(Class superclass, Map<String, Class> propertyMap) {
        this.target = generateBean(superclass, propertyMap);
        this.beanMap = BeanMap.create(this.target);
    }

    public void setValue(String property, Object value) {
        beanMap.put(property, value);
    }

    public Object getValue(String property) {
        return beanMap.get(property);
    }

    public Object getTarget() {
        return this.target;
    }

    /**
     * 根據屬性生成對象
     */
    private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
        BeanGenerator generator = new BeanGenerator();
        if (null != superclass) {
            generator.setSuperclass(superclass);
        }
        BeanGenerator.addProperties(generator, propertyMap);
        return generator.create();
    }
}

@Slf4j
class ReflectUtil {

    public static Object getObject(Object dest, Map<String, Object> newValueMap) throws InvocationTargetException, IllegalAccessException {
        PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();

        //1.獲取原對象的字段數組
        PropertyDescriptor[] descriptorArr = propertyUtilsBean.getPropertyDescriptors(dest);

        //2.遍歷原對象的字段數組,並將其封裝到Map
        Map<String, Class> oldKeyMap = new HashMap<>(4);
        for (PropertyDescriptor it : descriptorArr) {
            if (!"class".equalsIgnoreCase(it.getName())) {
                oldKeyMap.put(it.getName(), it.getPropertyType());
                newValueMap.put(it.getName(), it.getReadMethod().invoke(dest));
            }
        }

        //3.將擴展字段Map合並到原字段Map中
        newValueMap.forEach((k, v) -> oldKeyMap.put(k, v.getClass()));

        //4.根據新的字段組合生成子類對象
        DynamicBean dynamicBean = new DynamicBean(dest.getClass(), oldKeyMap);

        //5.放回合並后的屬性集合
        newValueMap.forEach((k, v) -> {
            try {
                dynamicBean.setValue(k, v);
            } catch (Exception e) {
                log.error("動態添加字段【值】出錯", e);
            }
        });
        return dynamicBean.getTarget();
    }
}

class User {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

四、效果

User:{
  "name" : "Daisy"
}
動態為User添加age之后,User:{
  "name" : "Daisy",
  "age" : 18
}

五、總結

  1. 使用了反射機制
  2. cglib的動態代理等技術(隱含意思就是被處理的類型不能被final關鍵字修飾)

六、持續優化

實踐過程中我對上述代碼進行了拆分與抽離,將主要邏輯封裝到了一個工具類中,下面是代碼:

  1. 測試類:
package com.yang.jmh.batch;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yang.jmh.rest.PropertyAppender;
import com.yang.jmh.rest.User0;

import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Yang
 * @date: 2020/11/4 00:12
 * @description:
 */
public class Kill0 {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    public static void main(String[] args) throws JsonProcessingException, InvocationTargetException, IllegalAccessException {

        User0 user = new User0();
        user.setName("Daisy");
        System.out.println(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(user));

        System.out.println("=====================================");

        Map<String, Object> propertiesMap = new HashMap<>(1);
        propertiesMap.put("age", 18);

        Object obj = PropertyAppender.generate(user, propertiesMap);
        System.err.println(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj));
    }

}

  1. 被添加字段的原始類:
package com.yang.jmh.rest;

/**
 * @author: Yang
 * @date: 2020/11/7 15:04
 * @description:
 */
public class User0 {

    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 工具類:
package com.yang.jmh.rest;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.beanutils.PropertyUtilsBean;
import org.springframework.cglib.beans.BeanGenerator;
import org.springframework.cglib.beans.BeanMap;

import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Yang
 * @date: 2020/11/7 14:47
 * @description:
 */
@Slf4j
public final class PropertyAppender {

    private static final class DynamicBean {

        private Object target;

        private BeanMap beanMap;

        private DynamicBean(Class superclass, Map<String, Class> propertyMap) {
            this.target = generateBean(superclass, propertyMap);
            this.beanMap = BeanMap.create(this.target);
        }

        private void setValue(String property, Object value) {
            beanMap.put(property, value);
        }

        private Object getValue(String property) {
            return beanMap.get(property);
        }

        private Object getTarget() {
            return this.target;
        }

        /**
         * 根據屬性生成對象
         */
        private Object generateBean(Class superclass, Map<String, Class> propertyMap) {
            BeanGenerator generator = new BeanGenerator();
            if (null != superclass) {
                generator.setSuperclass(superclass);
            }
            BeanGenerator.addProperties(generator, propertyMap);
            return generator.create();
        }
    }

    public static Object generate(Object dest, Map<String, Object> newValueMap) throws InvocationTargetException, IllegalAccessException {
        PropertyUtilsBean propertyUtilsBean = new PropertyUtilsBean();

        //1.獲取原對象的字段數組
        PropertyDescriptor[] descriptorArr = propertyUtilsBean.getPropertyDescriptors(dest);

        //2.遍歷原對象的字段數組,並將其封裝到Map
        Map<String, Class> oldKeyMap = new HashMap<>(4);
        for (PropertyDescriptor it : descriptorArr) {
            if (!"class".equalsIgnoreCase(it.getName())) {
                oldKeyMap.put(it.getName(), it.getPropertyType());
                newValueMap.put(it.getName(), it.getReadMethod().invoke(dest));
            }
        }

        //3.將擴展字段Map合並到原字段Map中
        newValueMap.forEach((k, v) -> oldKeyMap.put(k, v.getClass()));

        //4.根據新的字段組合生成子類對象
        DynamicBean dynamicBean = new DynamicBean(dest.getClass(), oldKeyMap);

        //5.放回合並后的屬性集合
        newValueMap.forEach((k, v) -> {
            try {
                dynamicBean.setValue(k, v);
            } catch (Exception e) {
                log.error("動態添加字段【值】出錯", e);
            }
        });
        return dynamicBean.getTarget();
    }
}
  1. 執行效果是這樣的

{
"name" : "Daisy"
}
=====================================
{
"name" : "Daisy",
"age" : 18
}


免責聲明!

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



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