寫業務代碼的同學,一定經常使用一個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);
}
}
}
}
}
}
③雖然代碼量看起來多一點,可實質上就是方式②的一個詳細解釋。
當然呢,①和②更具通用性,③的效率就高一點。那麼當一個產品的功能十分穩定以後,想要進行效率上的提升,那麼①②替換爲③,不失爲一個思路。
總結一下:大道至簡