对于线上已经在用的接口,要动态的扩展返回数据的字段,并且不同的调用商需要的字段不一样,为了A客户增加了一个字段,不能把字段返回给B,防止B客户那边也要升级接口。对于开始加一两个字段我还可以接受:每次返回不同的实例,渐渐的我觉得力不从心了,代码重复太多了,就想到了能不能动态的添加字段呢?
首先引入maven依赖:
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8</version>
</dependency>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib-nodep</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.22</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
代码中使用:
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Maps;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import net.sf.cglib.beans.BeanGenerator;
import net.sf.cglib.beans.BeanMap;
import org.apache.commons.beanutils.PropertyUtilsBean;
import java.beans.PropertyDescriptor;
import java.util.Map;
/**
* @author: jack
* @Date: 2019/5/15 21:30
* @Description: 动态添加类的属性
*/
public class ReflectUtil {
public static Object getTarget(Object dest, Map<String, Object> addProperties) {
PropertyUtilsBean propertyUtilsBean =new PropertyUtilsBean();
PropertyDescriptor[] descriptors = propertyUtilsBean.getPropertyDescriptors(dest);
Map<String, Class> propertyMap = Maps.newHashMap();
for(PropertyDescriptor d : descriptors) {
if(!"class".equalsIgnoreCase(d.getName())) {
propertyMap.put(d.getName(), d.getPropertyType());
}
}
// add extra properties
addProperties.forEach((k, v) -> propertyMap.put(k, v.getClass()));
// new dynamic bean
DynamicBean dynamicBean =new DynamicBean(dest.getClass(), propertyMap);
// add old value
propertyMap.forEach((k, v) -> {
try{
// filter extra properties
if(!addProperties.containsKey(k)) {
dynamicBean.setValue(k, propertyUtilsBean.getNestedProperty(dest, k));
}
}catch (Exception e) {
e.printStackTrace();
}
});
// add extra value
addProperties.forEach((k, v) -> {
try{
dynamicBean.setValue(k, v);
}catch (Exception e) {
e.printStackTrace();
}
});
Object target = dynamicBean.getTarget();
return target;
}
public static 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);
}
/**
* bean 添加属性和值
*
* @param property
* @param value
*/
public void setValue(String property, Object value) {
beanMap.put(property, value);
}
/**
* 获取属性值
*
* @param property
* @return
*/
public Object getValue(String property) {
return beanMap.get(property);
}
/**
* 获取对象
*
* @return
*/
public Object getTarget() {
return this.target;
}
/**
* 根据属性生成对象
*
* @param superclass
* @param propertyMap
* @return
*/
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();
}
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Student {
private String name;
private String email;
}
public static void main(String[] args) throws Exception{
Student student = Student.builder().name("jack").email("xy123zk@163.com").build();
System.out.println(student.toString());
Map<String,Object> properties = Maps.newHashMap();
properties.put("address","浙江杭州");
properties.put("age",26);
ObjectMapper mapper = new ObjectMapper();
String json = mapper.writeValueAsString(getTarget(student,properties));
System.out.println(json);
}
}
结果:
在spring集成的web项目可以直接使用spring提供的类,可以把依赖的
net.sf.cglib.beans.BeanGenerator
和net.sf.cglib.beans.BeanMap
直接换成org.springframework.cglib.beans.BeanGenerator
和org.springframework.cglib.beans.BeanMap