写业务代码的同学,一定经常使用一个API:
org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)
从一个实例中拷贝属性值到另外一个实例(可以是相同class或不同class)
对于写业务代码同学来说,确实很“省”代码。员外近仨月就节省了很多代码。
例如,历史文章中有一篇,就是自己为了偷懒,写的一个工具方法:
传送门
昨天review同事的一段代码(压测报告响应时间长),业务逻辑非常简单:数据库查询数据(POJO List),然后copyProperties到VO List。也就是数据量会大一些(目前返回测试数据千条级别,企业级应用,单条数据的字段值也蛮多)。同事采用的是序列化与反序列化的方式进行属性copy的。
既然业务逻辑(不好意思称之为算法)简单,为什么会”慢”呢?
自己能想到对象(属性值)copy的方式有三种:
①:基于ObjectMapper做序列化与反序列化
source和target field大面积相同,例如一个dto对应的vo。
public class BeanUtils {
private static Logger logger = LogManager.getLogger(CollectionsUtils.class);
private static ObjectMapper objectMapper = new ObjectMapper();
/**
* 从类实例中copy属性值
* 并返回一个指定类的实例作为返回值
* 基于{@link ObjectMapper}进行序列化与反序列化
*
* @param source
* @param type
* @param <T>
* @return
* @throws IOException
*/
public static <T, E> T copyProperties(E source, Class<T> type) {
String jsonString = null;
try {
jsonString = objectMapper.writeValueAsString(source);
return objectMapper.readValue(jsonString, type);
} catch (JsonProcessingException e) {
logger.error(e.getMessage(), e);
throw new RuntimeException();
} catch (IOException e) {
logger.error(e.getMessage(), e);
throw new RuntimeException();
}
}
}
②Spring中的接口:
org.springframework.beans.BeanUtils#copyProperties(java.lang.Object, java.lang.Object)
当然还有其它API,不做列举
③Getter/Setter方法使用
于是想着对三种方式进行实践测试:
public class User implements Cloneable{
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
public class UserMirror implements Cloneable{
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", sex='" + sex + '\'' +
'}';
}
}
public class BeanUtilsTest {
private static final int count = 1000000;
@Test
public void copyProperties() throws IOException {
System.out.println(String.format("拷贝数据:%d条", count));
this.copyBySerialize();
this.copyByReflex();
this.copyByMethod();
}
private List<UserMirror> copyBySerialize() throws IOException {
long begin = System.currentTimeMillis();
User user = new User();
user.setName("Young");
user.setSex("male");
List<UserMirror> list = new ArrayList<>(count);
for (int index = 0; index < count; index++) {
list.add(BeanUtils.copyProperties(user, UserMirror.class));
}
long end = System.currentTimeMillis();
System.out.println(String.format("序列化反序列化方式耗时:%d millis", end - begin));
return list;
}
private List<UserMirror> copyByReflex() throws IOException {
long begin = System.currentTimeMillis();
User user = new User();
user.setName("Young");
user.setSex("male");
List<UserMirror> list = new ArrayList<>(count);
for (int index = 0; index < count; index++) {
UserMirror userMirror = new UserMirror();
org.springframework.beans.BeanUtils.copyProperties(user, userMirror);
list.add(userMirror);
}
long end = System.currentTimeMillis();
System.out.println(String.format("反射方式耗时:%d millis", end - begin));
return list;
}
/**
* Getter/Setter
*
* @return
* @throws IOException
*/
private List<UserMirror> copyByMethod(){
long begin = System.currentTimeMillis();
User user = new User();
user.setName("Young");
user.setSex("male");
List<UserMirror> list = new ArrayList<>(count);
for (int index = 0; index < count; index++) {
UserMirror userMirror = new UserMirror();
userMirror.setName(user.getName());
userMirror.setSex(user.getSex());
list.add(userMirror);
}
long end = System.currentTimeMillis();
System.out.println(String.format("Getter/Setter方式耗时:%d millis", end - begin));
return list;
}
}
然后来看看测试结果:
然后用数据来说说化:
从效率来看③优于②优于①,思考一下原因:
①为什么最慢?看看方法,细心点可以发现有一个IOException异常处理,因为序列化与反序列化是需要涉及到IO开销的。
②看看API的源码:其实质还是利用反射来调用Getter/Setter方法
/**
* Copy the property values of the given source bean into the given target bean.
* <p>Note: The source and target classes do not have to match or even be derived
* from each other, as long as the properties match. Any bean properties that the
* source bean exposes but the target bean does not will silently be ignored.
* @param source the source bean
* @param target the target bean
* @param editable the class (or interface) to restrict property setting to
* @param ignoreProperties array of property names to ignore
* @throws BeansException if the copying failed
* @see BeanWrapper
*/
private static void copyProperties(Object source, Object target, @Nullable Class<?> editable,
@Nullable String... ignoreProperties) throws BeansException {
Assert.notNull(source, "Source must not be null");
Assert.notNull(target, "Target must not be null");
Class<?> actualEditable = target.getClass();
if (editable != null) {
if (!editable.isInstance(target)) {
throw new IllegalArgumentException("Target class [" + target.getClass().getName() +
"] not assignable to Editable class [" + editable.getName() + "]");
}
actualEditable = editable;
}
PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable);
List<String> ignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null);
for (PropertyDescriptor targetPd : targetPds) {
Method writeMethod = targetPd.getWriteMethod();
if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) {
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName());
if (sourcePd != null) {
Method readMethod = sourcePd.getReadMethod();
if (readMethod != null &&
ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) {
try {
if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) {
readMethod.setAccessible(true);
}
Object value = readMethod.invoke(source);
if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) {
writeMethod.setAccessible(true);
}
writeMethod.invoke(target, value);
}
catch (Throwable ex) {
throw new FatalBeanException(
"Could not copy property '" + targetPd.getName() + "' from source to target", ex);
}
}
}
}
}
}
③虽然代码量看起来多一点,可实质上就是方式②的一个详细解释。
当然呢,①和②更具通用性,③的效率就高一点。那么当一个产品的功能十分稳定以后,想要进行效率上的提升,那么①②替换为③,不失为一个思路。
总结一下:大道至简