有很多场景需要运行时创建对象,比如Copy对象到指定类型的对象中,比如根据指定的字段和值创建指定类型的对像。使用JDK自带的反射(java.lang.reflect)或者自省(java.beans.Introspector)都可以可实现。自省的方式会对类型信息做缓存因而效率更加高,推荐优先使用。
- copy对象
两个步骤完成,一是动态创建指定类型的对象,二是获取据源对象属性的值并赋到新对象对应的属性上。
public static void doCopyNotNull(Class<?> clazz, Object source, Object target) {
List<Field> fieldArray = getAllFields(clazz);
if (fieldArray != null && fieldArray.size() > 0) {
for (Field field : fieldArray) {
try {
// log.info("set field name=[{}]",field.getName());
if (Modifier.isFinal(field.getModifiers())) {
// log.info("Modifier is final");
continue;
}
field.setAccessible(true);
Object value = field.get(source);
// log.info("the value is [{}]",value);
if (value != null) {
// log.info("field name=[{}] do set value [{}]",field.getName(),value);
field.set(target, value);
}
} catch (IllegalArgumentException e) {
log.error("局部更新失败", e);
} catch (IllegalAccessException e) {
log.error("局部更新失败", e);
}
}
}
}
public static List<Field> getAllFields(Class clazz) {
List<Field> resultFieldList = Lists.newArrayList();
while (clazz != null && clazz != Object.class) {
Field[] fields = clazz.getDeclaredFields();
resultFieldList.addAll(Arrays.asList(fields));
clazz = clazz.getSuperclass();
}
return resultFieldList;
}
- 使用自省创建动态创建对象。
使用场景:创建指定类型的对象并根据key为字段名称value为对应值的java.util.Map初始化对象,对象完成返回给调用方。
特别要强调的是指定的java类型需要符合java bean命名规则。
2.1. 获取指定Class的key为filed的名称,value为PropertyDescriptor的map
/**
* 返回指定key为field名称,value为PropertyDescriptor的map
*
* @param clazz
* @return
*/
public static Map<String, PropertyDescriptor> getPropertyDescriptorMap(Class clazz) {
Map<String, PropertyDescriptor> map = Maps.newHashMap();
try {
BeanInfo beanInfo = Introspector.getBeanInfo(clazz);
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
if (propertyDescriptors != null && propertyDescriptors.length > 0) {
for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
map.put(propertyDescriptor.getName(), propertyDescriptor);
}
}
} catch (IntrospectionException e) {
e.printStackTrace();
}
return map;
}
2.2 . 创建对象并初始化对象
/**
* 创建指定类型的对象并根据传入的字段和对应的值给对象赋值。
*
* @param fieldValueMap
* @param clazz
* @param <T>
* @return
*/
public static <T> T createBean(Map<String, Object> fieldValueMap, Class<T> clazz) {
if (fieldValueMap == null | fieldValueMap.isEmpty()) {
return null;
}
try {
//创建对象
T obj = clazz.newInstance();
//给对象赋值
Map<String, PropertyDescriptor> propertyDescriptorMap = getPropertyDescriptorMap(clazz);
fieldValueMap.forEach((k, v) -> {
if (v == null) {
return;
}
PropertyDescriptor propertyDescriptor = propertyDescriptorMap.get(k);
if (propertyDescriptor==null){
log.warn("类[{}]中没有字段[{}],对应的值[{}]将无法设置.请检查是否有问题.",clazz,k,v);
return;
}
try {
if (propertyDescriptor.getPropertyType() == BigDecimal.class && v.getClass() != BigDecimal.class) {
v = new BigDecimal(v.toString());
}
propertyDescriptor.getWriteMethod().invoke(obj, v);
} catch (IllegalAccessException | InvocationTargetException e) {
new ServiceException("创建对象失败,异常如下:", e);
}catch(Throwable e){
new ServiceException("创建对象失败,异常如下:", e);
}
});
return obj;
} catch (InstantiationException | IllegalAccessException e) {
new ServiceException("创建对象失败", e);
}
return null;
}
- fieldValueMap:以property名称为key值为value的map。
- clazz:创建对象的类型。
推荐使用自省的方式创建对象,因为自省会缓存类型信息,从而不用每次调用重新获取类型信息,因而可以提高在多次使用时的性能。
查看源码如下:
public static BeanInfo getBeanInfo(Class<?> beanClass)
throws IntrospectionException
{
if (!ReflectUtil.isPackageAccessible(beanClass)) {
return (new Introspector(beanClass, null, USE_ALL_BEANINFO)).getBeanInfo();
}
ThreadGroupContext context = ThreadGroupContext.getContext();
BeanInfo beanInfo;
synchronized (declaredMethodCache) {
beanInfo = context.getBeanInfo(beanClass);
}
if (beanInfo == null) {
beanInfo = new Introspector(beanClass, null, USE_ALL_BEANINFO).getBeanInfo();
synchronized (declaredMethodCache) {
context.putBeanInfo(beanClass, beanInfo);
}
}
return beanInfo;
}
注意: synchronized (declaredMethodCache) {
beanInfo = context.getBeanInfo(beanClass);
}
首先从缓存中获取bean信息,如果不存在才会重新获取,因此创建多个类型相同的对象时可以大大提高性能 。