Java动态修改类添加字段属性(慎用!)

2018 11~15 min

Java动态修改类添加字段属性(慎用!)

需求背景

SpringAop切面实践-返回数据过滤填充 中针对过滤的数据需要给前端一个统一的字段进行标识展示,但是这就涉及到不同的类,而且让所涉及到的类中都加上一个与自己业务无关的字段,就显得很是冗余,所以加动态加字段的逻辑就放到了切面切面处理完成时候针对列表中的每条数据,进行字段追加的操作。

来吧展示


    /**
     * 根据远程调用的结果来处理按钮到时显示不显示
     *
     * @param rows
     * @param lists
     * @param businessKey
     * @return void
     * @author <a href="mailto:[email protected]">文刀草乙</a>
     * @date 2021/11/4 9:53
     */
    public List<Object> handleButtonShow(List<?> rows, List<ProcListHandleData> lists, String businessKey) {
        List<Object> objects = new ArrayList<>();
        for (Object row : rows) {
            try {
                String id = Objects.requireNonNull(getGetMethod(row, businessKey)).toString();
                if (StringUtils.isNotBlank(id)) {
                    // 得到切面返回的数据
                    ProcListHandleData handleData = (ProcListHandleData)lists.stream().filter(e -> Objects.equals(id, String.valueOf(e.getBusinessKey()))).distinct().toArray()[0];
                    // 将要动态增加的字段 放入到map中
                    Map<String, Object> propertiesMap = new HashMap<>(1);
                    propertiesMap.put("showAuditButton", handleData.getShowAuditButton());
                    Object obj = PropertyAppender.generate(row, propertiesMap);
                    objects.add(obj);
                }
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return objects;
    }

PropertyAppender.generate(row, propertiesMap) 动态添加字段逻辑

添加思路:

  • 将原对象和扩展字段封装为字段map
  • 基于字段map和原对象创建其子类对象
  • 重新将原字段值和扩展字段值赋给子类对象
  • 返回子类对象
    所需依赖:
(必须显式添加)
<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>

主要代码:

@Slf4j
public 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();
        }
    }

    // 主要的生成方法 dest原对象,newValueMap添加的新的字段属性
    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<>();
        for (PropertyDescriptor it : descriptorArr) {
            // 此处判断只要不是java.lang.Class 这个类,Class类无法继承创建子类,将原对象中的所有的字段属性都放到Map中
            if (!"class".equalsIgnoreCase(it.getName())) {
                oldKeyMap.put(it.getName(), it.getPropertyType());
                newValueMap.put(it.getName(), it.getReadMethod().invoke(dest));
            }
        }

        //3.将扩展字段Map合并到原字段Map中、
        for (Map.Entry<String, Object> entry : newValueMap.entrySet()) {
            if (!Objects.isNull(entry.getValue())) {
                oldKeyMap.put(entry.getKey(), entry.getValue().getClass());
            }
        }

        //        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();
    }
}

总结

  • 针对这种修改实体类结构的慎用,尤其是涉及到其他开发人员的实体类,一旦出现情况就会很难排查
  • 切面内的数据处理对于效率和损耗也是非常大的,要把控接口的响应时间