zoukankan      html  css  js  c++  java
  • 动态地给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 }

  • 相关阅读:
    pycharm激活(JetBrains IDEA 系列产品通用xx方法(license server))
    纯css美化下拉框、复选框以及单选框样式并用jquery获取到其被选中的val
    字符串替换
    jquery 倒计时效果
    js 数字前自动补零
    css 常用的绝对定位元素水平垂直居中的方法
    webapp中绝对定位/固定定位与虚拟键盘冲突的问题
    css 兼容各种iPhone
    使用 position:sticky 实现粘性布局
    jquery判断点击鼠标左、中、右键事件
  • 原文地址:https://www.cnblogs.com/JaxYoun/p/13923703.html
Copyright © 2011-2022 走看看