開頭聊幾句
- 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