Java常用屬性拷貝工具類使用總結
開頭聊幾句
- 1、網上很多的技術文章和資料是有問題的,要學會辨證的看待,不能隨便就拿來用,起碼要自己驗證一下
- 2、關注當下,關注此刻,如果你真正閱讀本篇文章,請花幾分鐘時間的注意力閱讀,相信你會有收穫的
- 3、文中程式碼沒有使用圖片展示,可能存在閱讀排版錯亂問題,請見諒,因為可能考慮到有其他夥伴需要拷貝程式碼,這樣比較方便
Java常用屬性拷貝工具類使用總結
對專案中經常使用的屬性拷貝工具類進行總結:
- org.apache.commons.beanutils.BeanUtils
- org.apache.commons.beanutils.PropertyUtils
- org.springframework.beans.BeanUtils
本文使用的工具類對應的版本:
commons-beanutils:1.9.4
spring-beans:5.0.7.RELEASE
欄位和屬性
首先明確下在Java中欄位和屬性的區別。
屬性是不是類裡最上邊的那些全域性變數嗎?比如:
public class UserTest{ private String userName; private String password; public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getHello() { return "hello"; } public void setHello(String str) { } }
上面 private String userName;private String password;。準確的來說它們應該稱為:欄位,而不是本次要講的屬性。
下面簡述一下:什麼是Java中的屬性?
Java中的屬性(property),通常可以理解為get和set方法,而欄位(field),通常叫做“類成員”,或“類成員變數”,有時也叫“域”,理解為“資料成員”,用來承載資料的。
直白點就是Java中的屬性是指:設定和讀取欄位的方法,也就是平常見到的set和get方法。只要是set和get開頭的方法在Java裡都認為它是屬性(請注意這句話,等下後邊會寫程式碼做驗證)
屬性名稱:就是set和get方法名 去掉"set"和"get"後的內容
public void setUserName(String userName) {
this.userName = userName;
}
它的屬性名稱是:userName(也就是方法名稱”setUserName”去掉“set”)
當然 setUserName和 getUserName 方法是指同一個屬性 UserName,
這裡再次提醒:欄位和屬性不是同一個東西。
程式碼驗證屬性
上面程式碼中還有一個 getHello
和 setHello
, JDK 中有個API Introspector
獲取的是java.beans.BeanInfo 類。這個類可以通過
java.beans.BeanInfo#getPropertyDescriptors : 獲取java bean 所有的屬性。
public static void main(String[] args) throws IntrospectionException {
BeanInfo beanInfo = Introspector.getBeanInfo(UserTest.class);
// 得到類中的所有的屬性描述器
PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors();
System.out.println("屬性的個數:" + pds.length);
for (PropertyDescriptor pd : pds) {
System.out.println("屬性:" + pd.getName());
}
}
結果:
屬性的個數:4
屬性:class
屬性:hello
屬性:password
屬性:userName
上面多了一個 class ,原因很簡單,因為Object類是所有類的父類,Object類裡有個方法叫 getClass();
所以這也驗證了咱們剛才說的: “只要是set或者get開頭的方法都叫屬性”
使用說明
default (即預設,什麼也不寫): 在同一包內可見,不使用任何修飾符。使用物件:類、介面、變數、方法。
public : 對所有類可見。使用物件:類、介面、變數、方法
private : 在同一類內可見。使用物件:變數、方法。 注意:不能修飾類(外部類)
protected : 對同一包內的類和所有子類可見。使用物件:變數、方法。 注意:不能修飾類(外部類)
org.springframework.beans.BeanUtils#copyProperties
1.基本型別和包裝型別會自動轉換, 方法名稱相同,返回值型別和引數型別不同,不進行復制,也不報錯_
2.支援指定忽略某些屬性不復制
3、支援類的修飾符 default 、 public
org.apache.commons.beanutils.PropertyUtils#copyProperties
1.基本型別和包裝型別會自動轉換
2.方法名稱相同,返回值型別和引數型別不同,複製失敗,會報錯,如下:
_argument type mismatch - had objects of type "java.lang.Double" but expected signature "java.lang.String"
3.只支援類的修飾符 public,如果是default 則直接不會進行轉換(注意內部類複製也要加public)
org.apache.commons.beanutils.BeanUtils#_copyProperties
1.基本型別和包裝型別會自動轉換
2.方法名稱相同,返回值型別和_ _引數型別不同,不復制,不報錯
3.只支援類的修飾符 public,如果是default 則直接不會進行轉換(注意內部類複製也要加public)
tips: Spring和apache的_copyProperties_屬性的方法源和目的引數的位置正好相反,所以導包和呼叫的時候都要注意一下。
// Apache
public static void
copyProperties(final Object dest, final Object orig)
// Spring
public static void
copyProperties(Object source, Object target)
效能參考:
Bean複製的幾種框架效能比較(Apache BeanUtils、PropertyUtils,Spring BeanUtils,Cglib BeanCopier)
摘要總結:Spring是在次數增多的情況下,效能較好,在資料較少的時候,效能比PropertyUtils的效能差一些。PropertyUtils的效能相對穩定,表現是呈現線性增長的趨勢。而Apache的BeanUtil的效能最差,無論是單次Copy還是大數量的多次Copy效能都不是很好。
使用的壓測工具備忘:Java使用JMH進行簡單的基準測試Benchmark : http://irfen.me/java-jmh-simple-microbenchmark/
根據上面的具體的分析還是使用 :org.springframework.beans.BeanUtils#copyProperties
原因:
1.這個方法在複製的時候不會因為屬性的不同而報錯,影響程式碼執行
2.效能方面也相對較好
其他Apache的兩個,
1、org.apache.commons.beanutils.PropertyUtils#copyProperties 複製會直接報錯
2、org.apache.commons.beanutils.BeanUtils#copyProperties 效能相對較差
原理探索
核心本質都是使用反射實現。具體的實現程式碼稍有不同。
Spring#BeanUtils
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);
}
}
}
}
}
}
1、獲取 目標物件 所有的屬性 targetPds
PropertyDescriptor_[] _targetPds = getPropertyDescriptors(actualEditable);
2、迴圈 targetPds ,並在源物件取出對應的屬性
PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName_())_;
3、r如果不是修飾不是public,暴力反射 ,然後使用對屬性進行設值
setAccessible_(true);// 暴力反射
writeMethod.invoke(target, value)_;
### apache.commons#BeanUtils
- org.apache.commons.beanutils.BeanUtilsBean#copyProperties
簡單擷取核心程式碼:
// org.apache.commons.beanutils.BeanUtilsBean#copyProperties
final PropertyDescriptor[] origDescriptors =
getPropertyUtils().getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if ("class".equals(name)) {
continue; // No point in trying to set an object's class
}
if (getPropertyUtils().isReadable(orig, name) &&
getPropertyUtils().isWriteable(dest, name)) {
try {
final Object value =
getPropertyUtils().getSimpleProperty(orig, name);
copyProperty(dest, name, value);
} catch (final NoSuchMethodException e) {
// Should not happen
}
}
}
// org.apache.commons.beanutils.BeanUtilsBean#copyProperty
getPropertyUtils().setSimpleProperty(target, propName, value);
// org.apache.commons.beanutils.PropertyUtilsBean#setSimpleProperty
invokeMethod(writeMethod, bean, values);
1、 獲取的是源物件的所有的屬性
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
2、如果屬性是class,不復制
if ("class".equals_(name)) { continue; // No point in trying to set an object's class}_
3、迴圈源物件的屬性,做一些檢驗
copyProperty(dest, name, value);
1、會檢驗目標物件是否有源物件的屬性,沒有跳過
2、獲取屬性的名稱型別
4、然後給目標物件設定,最終還是使用反射
method.invoke_(bean, values)_;
apache.commons#PropertyUtils
- org.apache.commons.beanutils.PropertyUtilsBean#copyProperties
簡單擷取核心程式碼:
// org.apache.commons.beanutils.PropertyUtilsBean#copyProperties
final PropertyDescriptor[] origDescriptors =
getPropertyDescriptors(orig);
for (PropertyDescriptor origDescriptor : origDescriptors) {
final String name = origDescriptor.getName();
if (isReadable(orig, name) && isWriteable(dest, name)) {
try {
final Object value = getSimpleProperty(orig, name);
if (dest instanceof DynaBean) {
((DynaBean) dest).set(name, value);
} else {
setSimpleProperty(dest, name, value);
}
} catch (final NoSuchMethodException e) {
if (log.isDebugEnabled()) {
log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
}
}
}
}
// org.apache.commons.beanutils.PropertyUtilsBean#invokeMethod
method.invoke(bean, values);
1、 獲取的是源物件的所有的屬性
final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
2、迴圈源物件的屬性,然後給目標物件設定,最終還是使用反射
總結
結合使用說明以及相關的效能和原理分析,建議使用 org.springframework.beans.BeanUtils#copyPropertie
參考資料
https://www.cnblogs.com/kaka/archive/2013/03/06/2945514.html
- Java反射——內省(Introspector)以及BeanUtils內省框架https://blog.csdn.net/ju_362204801/article/details/90672396