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. 新的类型转换器的定义以及注册:

          1.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.2  然后对于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.3 定义完转换类之后就是注册了:

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);
	}
}

               2. 类型转换的入口方法适配

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

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里面就是重构之后的代码,对于所获得的转换器做类型判断,不同的转换器调用不同的转换方法。


3. 对外方法的封装:

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,高下立判啊。


评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值