一、場景
- 需求的敘述比較抽象難懂,總之,最后想要的結果就是動態的給對象添加屬性,然后返回給前台。
二、思路
- 搜了一圈,還真有,基於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.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
}
五、總結
- 使用了反射機制
- cglib的動態代理等技術(隱含意思就是被處理的類型不能被final關鍵字修飾)
六、持續優化
實踐過程中我對上述代碼進行了拆分與抽離,將主要邏輯封裝到了一個工具類中,下面是代碼:
- 測試類:
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));
}
}
- 被添加字段的原始類:
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;
}
}
- 工具類:
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();
}
}
- 執行效果是這樣的
{
"name" : "Daisy"
}
=====================================
{
"name" : "Daisy",
"age" : 18
}