Java中几种属性copy工具比较
属性copy是日常开发中比较常见的操作,如DO转DTO等。常见的属性copy工具类方法有:
- 直接调用类的getter setter方法(效率高,代码长)
- Spring提供的BeanUtils.copyProperties(Object source, Object target[,String… ignoreProperties])工具方法。基于Java的反射机制,支持ignore操作
- Apache提供的BeanUtils.copyProperties(),类似于Spring的BeanUtils,支持属性类型自动转换的功能,效率较低与Spring的BeanUtils
- Apache提供的PropertyUtils.copyProperties(Object dest, Object orig)工具方法,不支持属性类型自动转换的功能,如果类型不同,会抛java.lang.IllegalArgumentException异常
- cglib的BeanCopier,使用动态代理,效率高
测试代码
source数据CopyBeanDO
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.math.BigInteger;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CopyBeanDO {
private int intValue;
private boolean boolValue;
private float floatValue;
private double doubleValue;
private long longValue;
private char charValue;
private byte byteValue;
private short shortValue;
private Integer integerValue;
private Boolean boolObjValue;
private Float floatObjValue;
private Double doubleObjValue;
private Long longObjValue;
private Short shortObjValue;
private Byte byteObjValue;
private BigInteger bigIntegerValue;
private BigDecimal bigDecimalValue;
private String stringValue;
public boolean getBoolValue() {
return boolValue;
}
public void setBoolValue(boolean boolValue) {
this.boolValue = boolValue;
}
}
target数据DTO
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.math.BigInteger;
/**
* Created by RuzzZZ on 2017/2/17.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class CopyBeanDTO implements Serializable {
private static final long serialVersionUID = 2977329629013865582L;
private int intValue;
private boolean boolValue;
private float floatValue;
private double doubleValue;
private long longValue;
private char charValue;
private byte byteValue;
private short shortValue;
private Integer integerValue;
private Boolean boolObjValue;
private Float floatObjValue;
private Double doubleObjValue;
private Long longObjValue;
private Short shortObjValue;
private Byte byteObjValue;
private BigInteger bigIntegerValue;
private BigDecimal bigDecimalValue;
private String stringValue;
public boolean getBoolValue() {
return boolValue;
}
public void setBoolValue(boolean boolValue) {
this.boolValue = boolValue;
}
public static void main(String[] args) {
System.out.println(System.currentTimeMillis());
}
}
测试main方法
import com.alibaba.fastjson.JSONObject;
import com.esotericsoftware.reflectasm.ConstructorAccess;
import org.apache.commons.beanutils.PropertyUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.cglib.beans.BeanCopier;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
public class BeanUtilsCopyDemo {
public static final Integer LOOP_TIME = 500000;
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
{
CopyBeanDO copyBeanDO = new CopyBeanDO(100, true, 3.21f, 3.21, 100L, 'c', (byte) 3, (short) 3, Integer.MIN_VALUE, Boolean.TRUE,
Float.MAX_VALUE, Double.MAX_VALUE, Long.MAX_VALUE, Short.MAX_VALUE, Byte.MAX_VALUE, BigInteger.ONE, BigDecimal.ONE, "test");
CopyBeanDTO copyBeanDTO = new CopyBeanDTO();
Long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_TIME; i++) {
BeanUtils.copyProperties(copyBeanDO, copyBeanDTO);
}
Long end = System.currentTimeMillis();
System.out.println("Spring.BeanUtils spend time : " + (end - startTime));
System.out.println("CarSnapInfoDTO : " + JSONObject.toJSONString(copyBeanDTO));
}
{
CopyBeanDO copyBeanDO = new CopyBeanDO(100, true, 3.21f, 3.21, 100L, 'c', (byte) 3, (short) 3, Integer.MIN_VALUE, Boolean.TRUE,
Float.MAX_VALUE, Double.MAX_VALUE, Long.MAX_VALUE, Short.MAX_VALUE, Byte.MAX_VALUE, BigInteger.ONE, BigDecimal.ONE, "test");
CopyBeanDTO copyBeanDTO = new CopyBeanDTO();
Long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_TIME; i++) {
PropertyUtils.copyProperties(copyBeanDO, copyBeanDTO);
}
Long end = System.currentTimeMillis();
System.out.println("Apache.PropertyUtils spend time : " + (end - startTime));
System.out.println("CarSnapInfoDTO : " + JSONObject.toJSONString(copyBeanDTO));
}
{
CopyBeanDO copyBeanDO = new CopyBeanDO(100, true, 3.21f, 3.21, 100L, 'c', (byte) 3, (short) 3, Integer.MIN_VALUE, Boolean.TRUE,
Float.MAX_VALUE, Double.MAX_VALUE, Long.MAX_VALUE, Short.MAX_VALUE, Byte.MAX_VALUE, BigInteger.ONE, BigDecimal.ONE, "test");
CopyBeanDTO copyBeanDTO = new CopyBeanDTO();
Long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_TIME; i++) {
copyBean(copyBeanDO, copyBeanDTO);
}
Long end = System.currentTimeMillis();
System.out.println("direct setter spend time : " + (end - startTime));
System.out.println("CarSnapInfoDTO : " + JSONObject.toJSONString(copyBeanDTO));
}
{
CopyBeanDO copyBeanDO = new CopyBeanDO(100, true, 3.21f, 3.21, 100L, 'c', (byte) 3, (short) 3, Integer.MIN_VALUE, Boolean.TRUE,
Float.MAX_VALUE, Double.MAX_VALUE, Long.MAX_VALUE, Short.MAX_VALUE, Byte.MAX_VALUE, BigInteger.ONE, BigDecimal.ONE, "test");
ConstructorAccess<CopyBeanDTO> constructorAccess = ConstructorAccess.get(CopyBeanDTO.class);
CopyBeanDTO copyBeanDTO = constructorAccess.newInstance();
BeanCopier copier = BeanCopier.create(CopyBeanDO.class, CopyBeanDTO.class, false);
Long startTime = System.currentTimeMillis();
for (int i = 0; i < LOOP_TIME; i++) {
copier.copy(copyBeanDO, copyBeanDTO, null);
}
Long end = System.currentTimeMillis();
System.out.println("BeanCopier : " + (end - startTime));
System.out.println("CarSnapInfoDTO : " + JSONObject.toJSONString(copyBeanDTO));
}
}
private static void copyBean(CopyBeanDO copyBeanDO, CopyBeanDTO copyBeanDTO) {
copyBeanDTO.setIntValue(copyBeanDO.getIntValue());
copyBeanDTO.setBoolValue(copyBeanDO.getBoolObjValue());
copyBeanDTO.setFloatValue(copyBeanDO.getFloatValue());
copyBeanDTO.setDoubleValue(copyBeanDO.getDoubleValue());
copyBeanDTO.setLongValue(copyBeanDO.getLongValue());
copyBeanDTO.setCharValue(copyBeanDO.getCharValue());
copyBeanDTO.setByteValue(copyBeanDO.getByteValue());
copyBeanDTO.setShortValue(copyBeanDO.getShortValue());
copyBeanDTO.setIntegerValue(copyBeanDO.getIntegerValue());
copyBeanDTO.setBoolObjValue(copyBeanDO.getBoolObjValue());
copyBeanDTO.setFloatObjValue(copyBeanDO.getFloatObjValue());
copyBeanDTO.setDoubleObjValue(copyBeanDO.getDoubleObjValue());
copyBeanDTO.setLongObjValue(copyBeanDO.getLongObjValue());
copyBeanDTO.setShortObjValue(copyBeanDO.getShortObjValue());
copyBeanDTO.setByteObjValue(copyBeanDO.getByteObjValue());
copyBeanDTO.setBigIntegerValue(copyBeanDO.getBigIntegerValue());
copyBeanDTO.setBigDecimalValue(copyBeanDO.getBigDecimalValue());
copyBeanDTO.setStringValue(copyBeanDO.getStringValue());
}
}
运行结果:
Spring.BeanUtils spend time : 1228
CarSnapInfoDTO : {"bigDecimalValue":1,"bigIntegerValue":1,"boolObjValue":true,"boolValue":true,"byteObjValue":127,"byteValue":3,"charValue":"c","doubleObjValue":1.7976931348623157E308,"doubleValue":3.21,"floatObjValue":3.4028235E38,"floatValue":3.21,"intValue":100,"integerValue":-2147483648,"longObjValue":9223372036854775807,"longValue":100,"shortObjValue":32767,"shortValue":3,"stringValue":"test"}
Apache.PropertyUtils spend time : 6642
CarSnapInfoDTO : {"boolValue":false,"byteValue":0,"charValue":"\u0000","doubleValue":0,"floatValue":0,"intValue":0,"longValue":0,"shortValue":0}
direct setter spend time : 15
CarSnapInfoDTO : {"bigDecimalValue":1,"bigIntegerValue":1,"boolObjValue":true,"boolValue":true,"byteObjValue":127,"byteValue":3,"charValue":"c","doubleObjValue":1.7976931348623157E308,"doubleValue":3.21,"floatObjValue":3.4028235E38,"floatValue":3.21,"intValue":100,"integerValue":-2147483648,"longObjValue":9223372036854775807,"longValue":100,"shortObjValue":32767,"shortValue":3,"stringValue":"test"}
BeanCopier : 9
CarSnapInfoDTO : {"bigDecimalValue":1,"bigIntegerValue":1,"boolObjValue":true,"boolValue":true,"byteObjValue":127,"byteValue":3,"charValue":"c","doubleObjValue":1.7976931348623157E308,"doubleValue":3.21,"floatObjValue":3.4028235E38,"floatValue":3.21,"intValue":100,"integerValue":-2147483648,"longObjValue":9223372036854775807,"longValue":100,"shortObjValue":32767,"shortValue":3,"stringValue":"test"}
Spring的BeanUtils、Apache的BeanUtils、Apache的PropertyUtils对比
对比代码
import lombok.Data;
@Data
public class TestA {
private int testInteger;
private String testStr;
private String testDiff;
}
import lombok.Data;
@Data
public class TestB {
private Integer testInteger;
private String testStr;
private Integer testDiff;
}
import java.lang.reflect.InvocationTargetException;
public class BeanUtilsTestDemo {
public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
TestA testA = new TestA();
testA.setTestStr("test12321");
testA.setTestDiff("10");
TestB testB1 = new TestB();
TestB testB2 = new TestB();
TestB testB3 = new TestB();
org.springframework.beans.BeanUtils.copyProperties(testA, testB1);
System.out.println(testB1);
org.apache.commons.beanutils.BeanUtils.copyProperties(testB2, testA);
System.out.println(testB2);
org.apache.commons.beanutils.PropertyUtils.copyProperties(testB3, testA);
System.out.println(testB3);
}
}
运行结果
TestB(testInteger=0, testStr=test12321, testDiff=null)
TestB(testInteger=0, testStr=test12321, testDiff=10)
Exception in thread "main" java.lang.IllegalArgumentException: Cannot invoke com.xzy.copy.TestB.setTestDiff on bean class 'class com.xzy.copy.TestB' - argument type mismatch - had objects of type "java.lang.String" but expected signature "java.lang.Integer"
at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2195)
at org.apache.commons.beanutils.PropertyUtilsBean.setSimpleProperty(PropertyUtilsBean.java:2108)
at org.apache.commons.beanutils.PropertyUtilsBean.copyProperties(PropertyUtilsBean.java:329)
at org.apache.commons.beanutils.PropertyUtils.copyProperties(PropertyUtils.java:219)
at com.xzy.copy.BeanUtilsTestDemo.main(BeanUtilsTestDemo.java:18)
Caused by: java.lang.IllegalArgumentException: argument type mismatch
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.apache.commons.beanutils.PropertyUtilsBean.invokeMethod(PropertyUtilsBean.java:2127)
... 4 more
基于cglib的copy工具类
cglib的性能最好,效率最高,几乎等价于直接调用set、get方法,但是其中创建BeanCopier和利用ASM的ConstructorAccess是比较耗时的操作,可以利用缓存减少不必要的耗时。 下面是一个基于以上原因而写的一个工具类
/**
* 快速拷贝
*/
public class WrappedBeanCopier {
private static final int MAX_CACHE_SIZE = 500;
private static final Map<String, BeanCopier> beanCopierCache = new ConcurrentHashMap<>();
private static final Map<String, ConstructorAccess> constructorAccessCache = new ConcurrentHashMap<>();
protected static void copyProperties(Object source, Object target) {
if (source == null || target == null) return;
BeanCopier copier = getBeanCopier(source.getClass(), target.getClass());
copier.copy(source, target, null);
}
private static BeanCopier getBeanCopier(Class sourceClass, Class targetClass) {
String beanKey = generateKey(sourceClass, targetClass);
BeanCopier copier = null;
if (!beanCopierCache.containsKey(beanKey)) {
copier = BeanCopier.create(sourceClass, targetClass, false);
if (beanCopierCache.size() > MAX_CACHE_SIZE) {
beanCopierCache.clear();
}
beanCopierCache.put(beanKey, copier);
} else {
copier = beanCopierCache.get(beanKey);
}
return copier;
}
private static String generateKey(Class<?> class1, Class<?> class2) {
return class1.getName() + class2.getName();
}
protected static <T> T copyProperties(Object source, Class<T> targetClass) {
if (source == null) return null;
T t = null;
try {
ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
t = constructorAccess.newInstance();
} catch (RuntimeException e) {
try {
t = targetClass.newInstance();
} catch (InstantiationException | IllegalAccessException e1) {
throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
}
}
copyProperties(source, t);
return t;
}
protected static <T> List<T> copyPropertiesOfList(List<?> sourceList, Class<T> targetClass) {
if (sourceList == null || sourceList.isEmpty()) {
return Collections.emptyList();
}
ConstructorAccess<T> constructorAccess = getConstructorAccess(targetClass);
List<T> resultList = new ArrayList<>(sourceList.size());
for (Object o : sourceList) {
T t = null;
try {
t = constructorAccess.newInstance();
copyProperties(o, t);
resultList.add(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return resultList;
}
private static <T> ConstructorAccess<T> getConstructorAccess(Class<T> targetClass) {
ConstructorAccess<T> constructorAccess = constructorAccessCache.get(targetClass.getName());
if(constructorAccess != null) {
return constructorAccess;
}
try {
constructorAccess = ConstructorAccess.get(targetClass);
if (constructorAccessCache.size() > MAX_CACHE_SIZE) {
constructorAccessCache.clear();
}
constructorAccessCache.put(targetClass.getName(),constructorAccess);
} catch (Exception e) {
throw new RuntimeException(format("Create new instance of %s failed: %s", targetClass, e.getMessage()));
}
return constructorAccess;
}
}