Java深度克隆


背景

 
  在实际开发中经常遇到属性克隆的问题,比如在表现层提交的Request DTO,可能需要在控制层被映射成多个Java Bean,再传递到逻辑层来进行相应的业务处理,那么如何才能简单而又快速的完成属性克隆呢?

对于仅仅包含简单属性的Java Bean来说,Apache Commons里的BeanUtils 是个不错的选择,不过前提是对应属性必须具备相同的属性名。但是对于复杂属性来说, 如果两个属性的类型不同,或者类型参数不同,使用BeanUtils的时候都要格外小心。

举个例子: InvoiceDTO和InvoiceBean里面都有两个属性amount和charges,类型定义如下:

InvoiceDTO  [ amount -> AmountDTO,  charges -> List<ChargeDTO>]
InvoiceBean [ amount ->AmountBean,  charges -> List<ChargeBean>]

这种情况下Apache BeanUtils无法正确克隆amount和charges,那我们应该怎么办呢?

 

解决方案

 

序列化与反序列化


利用Jason做过渡,这种做法最优雅,也最简单,但是,多转换多IO, 性能不行。
 

扩展Apache BeanUtils


通过扩展,让它支持复杂的类型转换,而不仅仅是支持简单的类型转换(比如BigDecimal和String的转换)。

要想进行正确扩展,首先得弄清楚BeanUtils本身的结构和工作原理。反编译commons的代码之后,发现它之所以能够支持简单类型转换,是因为内嵌了一系列的类型转换器,这些转换器在初始化的时候与其对应的目标类型被一起缓存到一个fast hashmap中,之后每次在利用内省进行属性设置的时候,会先获取目标属性的类型所对应的类型转换器,转换之后再将结果其set到目标对象上。

了解了BeanUtils的工作原理之后,我们发现扩展的两个关键点:新的类型转换器的定义以及注册,类型转换的入口方法适配。

 

新的类型转换器的定义以及注册:

 

  1. 首先是定义: 本例子中需要转换的是带泛型参数的类型,可以抽象一个ParameterizedTypeConverter接口:
import org.apache.commons.beanutils.Converter;
 
public abstract interface ParameterizedTypeConverter extends Converter {
	public abstract Object convert(Class targetClass, Class targetGenericType,
			Object sourceObj);
}
  1. 然后对于List类型定义一个实现类ListConverter:
import java.util.ArrayList; 
import java.util.List;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.beanutils.ConversionException;
import org.springframework.util.CollectionUtils;
 
@SuppressWarnings("all")
public final class ListConverter implements ParameterizedTypeConverter {
 
	@Override
	public Object convert(Class type, Object valuet) {
		return null;
	}
 
	@Override
	public Object convert(Class targetClass, Class destGenericType,
			Object origValue) {
 
		if (!(origValue instanceof List)) {
			throw new ConversionException("Invalid List value");
		}
 
		List origList = List.class.cast(origValue);
		if (!CollectionUtils.isEmpty(origList)) {
 
			Class origGenericType = origList.get(0).getClass();
			if (origGenericType.equals(destGenericType)) {
				return origValue;
			} else {
				List model = new ArrayList();
				try {
					for (Object singleValue : origList) {
						Object dest = destGenericType.newInstance();
						Object orig = origGenericType.cast(singleValue);
						BeanUtils.copyProperties(dest, orig);
						model.add(dest);
					}
				} catch (Exception e) {
					throw new ConversionException(e);
				}
				return model;
			}
		}
 
		return origValue;
	}
}
  1. 定义完转换类之后就是注册了:
import java.util.List; 
 
import org.apache.commons.beanutils.ConvertUtilsBean;
 
public class ARPConvertUtilsBean extends ConvertUtilsBean {
 
	@Override
	public void deregister() {
		super.deregister();
		register(new ListConverter(), List.class);
	}
}

 

类型转换的入口方法适配

 
  我们看到新增的转换器转换属性需要一些额外的参数,那么对应调用转换器的方法也得修改:

import java.beans.PropertyDescriptor; 
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
 
import org.apache.commons.beanutils.BeanUtilsBean;
import org.apache.commons.beanutils.ContextClassLoaderLocal;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaClass;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.PropertyUtilsBean;
 
public class ARPBeanUtilsBean extends BeanUtilsBean {
 
	public ARPBeanUtilsBean() {
		super(new ARPConvertUtilsBean(), new PropertyUtilsBean());
	}
 
	public static synchronized BeanUtilsBean getInstance() {
		return ((BeanUtilsBean) new ContextClassLoaderLocal() {
			// Creates the default instance used when the context classloader is
			// unavailable
			protected Object initialValue() {
				return new ARPBeanUtilsBean();
			}
		}.get());
	}
 
	@Override
	public void copyProperty(Object bean, String name, Object value)
			throws IllegalAccessException, InvocationTargetException {
 
		Object target = bean;
		int delim = name.lastIndexOf(46);
		if (delim >= 0) {
			try {
				target = getPropertyUtils().getProperty(bean,
						name.substring(0, delim));
			} catch (NoSuchMethodException e) {
				return;
			}
			name = name.substring(delim + 1);
		}
 
		String propName = null;
		Class type = null;
		int index = -1;
		String key = null;
 
		propName = name;
		int i = propName.indexOf(91);
		if (i >= 0) {
			int k = propName.indexOf(93);
			try {
				index = Integer.parseInt(propName.substring(i + 1, k));
			} catch (NumberFormatException e) {
			}
			propName = propName.substring(0, i);
		}
		int j = propName.indexOf(40);
		if (j >= 0) {
			int k = propName.indexOf(41);
			try {
				key = propName.substring(j + 1, k);
			} catch (IndexOutOfBoundsException e) {
			}
			propName = propName.substring(0, j);
		}
 
		if (target instanceof DynaBean) {
			DynaClass dynaClass = ((DynaBean) target).getDynaClass();
			DynaProperty dynaProperty = dynaClass.getDynaProperty(propName);
			if (dynaProperty == null) {
				return;
			}
			type = dynaProperty.getType();
		} else {
			PropertyDescriptor descriptor = null;
			try {
				descriptor = getPropertyUtils().getPropertyDescriptor(target,
						name);
 
				if (descriptor == null)
					return;
			} catch (NoSuchMethodException e) {
				return;
			}
			type = descriptor.getPropertyType();
			if (type == null) {
				return;
			}
		}
 
		if (index >= 0) {
			Converter converter = getConvertUtils().lookup(
					type.getComponentType());
			if (converter != null) {
				value = converter.convert(type, value);
			}
			try {
				getPropertyUtils().setIndexedProperty(target, propName, index,
						value);
			} catch (NoSuchMethodException e) {
				throw new InvocationTargetException(e, "Cannot set " + propName);
			}
		} else if (key != null) {
			try {
				getPropertyUtils().setMappedProperty(target, propName, key,
						value);
			} catch (NoSuchMethodException e) {
				throw new InvocationTargetException(e, "Cannot set " + propName);
			}
		} else {
			Converter converter = getConvertUtils().lookup(type);
			if (converter != null) {
				if (converter instanceof ParameterizedTypeConverter) {
					try {
						ParameterizedType targetActualType = (ParameterizedType) target
								.getClass().getDeclaredField(propName)
								.getGenericType();
						Class<?> targetActualClass = (Class<?>) targetActualType
								.getActualTypeArguments()[0];
						value = ((ParameterizedTypeConverter) converter)
								.convert(type, targetActualClass, value);
					} catch (Exception e) {
						return;
					}
 
				} else {
					try {
						value = converter.convert(type, value);
					} catch (Exception e) {
						return;
					}
				}
			}else {
				try {
					value = ARPBeanUtils.cloneBean(type, value);
				} catch (Exception e) {
					return;
				}
			}
 
			try {
				getPropertyUtils().setSimpleProperty(target, propName, value);
			} catch (NoSuchMethodException e) {
				throw new InvocationTargetException(e, "Cannot set " + propName);
			}
		}
	}
}

可以看到最后一大段else里面就是重构之后的代码,对于所获得的转换器做类型判断,不同的转换器调用不同的转换方法。

 

对外封装

 

import org.apache.commons.beanutils.BeanUtils; 
import org.apache.commons.beanutils.BeanUtilsBean;
 
/**
 * @ClassName: ARPBeanUtils
 * @Description: An extension of Apache common BeanUtils, can do the conversion
 *               between POJOs with attributes of different generic type, like
 *               from: InvoiceDTO-List<ChargeDTO> to
 *               InvoiceBean-List<ChargeBean>
 * @author ZHUGA3
 * @date Sep 27, 2012 1:33:25 PM
 * 
 */
public final class ARPBeanUtils extends BeanUtils {
 
	private static BeanUtilsBean utilBean = ARPBeanUtilsBean.getInstance();
 
	public static void copyProperties(Object dest, Object orig) {
		try {
			utilBean.copyProperties(dest, orig);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
 
	/*
	 * a non-argument constructor must be supplied in destClass
	 */
	public static <T> T cloneBean(Class<T> destClass, Object orig) {
		try {
			T dest = destClass.newInstance();
			copyProperties(dest, orig);
			return dest;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}
}

 

总结


至此,大功告成,测试一下,能快速完成例子中的克隆,有多快?和第一种方法对比一下:同时转换1W次的时候,使用扩展后的 ARPBeanUtils 需要200ms,而通过Jason,即使是使用效率最高的Jackson,转换1W次也需要6s,高下立判啊
 
 


See also