动态地给Java对象添加字段并赋值
动态地给Java对象添加字段并赋值
一、场景
需求的叙述比较抽象难懂,总之,最后想要的结果就是动态的给对象添加属性,然后返回给前台。
二、思路
搜了一圈,还真有,基于cglib、commons-beanutils库实现
将原对象和扩展字段封装为字段map
基于字段map和原对象创建其子类对象
重新将原字段值和扩展字段值赋给子类对象
返回子类对象
三、实现
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>
代码实现
为了能集中管理,我将所有涉及的类都写在了同一个源文件中,如需测试,可以将下面代码整体拷入一个Java类,解决好依赖,直接执行main函数
package com.tt;
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;
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
}
五、总结
使用了反射机制
cglib的动态代理等技术(隐含意思就是被处理的类型不能被final关键字修饰)
六、持续优化
实践过程中我对上述代码进行了拆分与抽离,将主要逻辑封装到了一个工具类中,下面是代码:
测试类:
package com.tt;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.tt.rest.PropertyAppender;
import com.tt.rest.User0;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
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));
}
}
被添加字段的原始类:
package com.tt;
public class User0 {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
工具类:
package com.tt;
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;
@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();
}
}
执行效果是这样的
{
"name" : "Daisy"
}
=====================================
{
"name" : "Daisy",
"age" : 18
}