动态地给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
}

posted @ 2020-11-04 01:33  JaxYoun  阅读(19409)  评论(2编辑  收藏  举报