摘要:前置知識在分析源碼前,我們先溫習一下以下的知識點。類在中萬物皆對象,而且我們在代碼中寫的每一個類也都是對象,是類的對象。總結一個看似簡單的工具類,其實里面包含的基礎的知識點非常多,包括類型信息反射線程安全引用類型類加載器等。
背景
在我們著手一個Java Web項目的時候,經常會遇到DO、VO、DTO對象之間的屬性拷貝,若采用get、set的方法來進行賦值的話,代碼會相當冗長丑陋,一般我們會采用Spring的BeanUtils類來進行屬性拷貝,其基本原理就是通過Java的反射機制,下面我們來看一下源碼的具體實現。
前置知識在分析源碼前,我們先溫習一下以下的知識點。
java.lang.Class類在Java中萬物皆對象,而且我們在代碼中寫的每一個類也都是對象,是java.lang.Class類的對象。所以,每個類都有自己的實例對象,而且它們自己也都是Class類的對象。
我們來看一下Class類的構造方法:
private Class(ClassLoader loader) { // Initialize final field for classLoader. The initialization value of non-null // prevents future JIT optimizations from assuming this final field is null. classLoader = loader; }
Class類的構造方法是私有的,只有JVM可以創建該類的對象,因此我們無法在代碼中通過new的方式顯示聲明一個Class對象。
但是,我們依然有其他方式獲得Class類的對象:
1.通過類的靜態成員變量
Class clazz = Test.class;
2.通過對象的getClass()方法
Class clazz = test.getClass();
3.通過Class的靜態方法forName()
// forName需要傳入類的全路徑 Class clazz = Class.forName("destiny.iron.api.model.Test");基本類型和包裝類型
基本類型和其對應的包裝類的Class對象是不相等的,即long.class != Long.class 。
PropertyDescriptor類PropertyDescriptor類表示的是標準形式的Java Bean通過存取器(即get set方法)導出的一個屬性,比如,我們可以通過以下方式,對對象的屬性進行賦值:
public class Person { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "Person{" + "name="" + name + """ + "}"; } public static void main(String[] args) throws Exception { Person test1 = new Person(); test1.setName("vvvv"); PropertyDescriptor pd = new PropertyDescriptor("name", test1.getClass()); Method setMethod = pd.getWriteMethod(); // 還有與Wirte方法對應的Read方法 setMethod.invoke(test1, "bbbbb"); System.out.print(test1); } }引用類型
Java中有strong、soft、weak、phantom四種引用類型,下面介紹一下soft引用和weak引用:
Soft Reference: 當對象是Soft reference可達時,向系統申請更多內存,GC不是直接回收它,而是當內存不足的時候才回收它。因此Soft reference適合用于構建一些緩存系統。
Weak Reference: 弱引用的強度比軟引用更弱一些,被弱引用關聯的對象只能生存到下一次GC發生之前。當垃圾收集器工作時,無論當前內存是否足夠,都會回收掉只被弱引用關聯的對象。
源碼分析private static void copyProperties(Object source, Object target, Class> editable, String... ignoreProperties) throws BeansException { // 檢查source和target對象是否為null,否則拋運行時異常 Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"); // 獲取target對象的類信息 Class> actualEditable = target.getClass(); // 若editable不為null,檢查target對象是否是editable類的實例,若不是則拋出運行時異常 // 這里的editable類是為了做屬性拷貝時限制用的 // 若actualEditable和editable相同,則拷貝actualEditable的所有屬性 // 若actualEditable是editable的子類,則只拷貝editable類中的屬性 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,getPropertyDescriptors這個方法請看下方 PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); ListignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); for (PropertyDescriptor targetPd : targetPds) { // 獲取該屬性對應的set方法 Method writeMethod = targetPd.getWriteMethod(); // 屬性的set方法存在 且 該屬性不包含在忽略屬性列表中 if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { // 獲取source類相同名字的PropertyDescriptor, getPropertyDescriptor的具體實現看下方 PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null) { // 獲取對應的get方法 Method readMethod = sourcePd.getReadMethod(); // set方法存在 且 target的set方法的入參是source的get方法返回值的父類或父接口或者類型相同 // 具體ClassUtils.isAssignable()的實現方式請看下面詳解 if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { try { //get方法是否是public的 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { //暴力反射,取消權限控制檢查 readMethod.setAccessible(true); } //獲取get方法的返回值 Object value = readMethod.invoke(source); // 原理同上 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } // 將get方法的返回值 賦值給set方法作為入參 writeMethod.invoke(target, value); } catch (Throwable ex) { throw new FatalBeanException( "Could not copy property "" + targetPd.getName() + "" from source to target", ex); } } } } } }
getPropertyDescriptors源碼:
public static PropertyDescriptor[] getPropertyDescriptors(Class> clazz) throws BeansException { // CachedIntrospectionResults類是對PropertyDescriptor的一個封裝實現,看forClass方法的實現 CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); return cr.getPropertyDescriptors(); } @SuppressWarnings("unchecked") static CachedIntrospectionResults forClass(Class> beanClass) throws BeansException { // strongClassCache的聲明如下: // strongClassCache = new ConcurrentHashMap, CachedIntrospectionResults>(64); // 即將Class作為key,CachedIntrospectionResults作為value的map, // 由于線程安全的需要,使用ConcurrentHashMap作為實現 CachedIntrospectionResults results = strongClassCache.get(beanClass); if (results != null) { return results; } // 若strongClassCache中不存在,則去softClassCache去獲取,softClassCache的聲明如下 // softClassCache = new ConcurrentReferenceHashMap , CachedIntrospectionResults>(64); // ConcurrentReferenceHashMap是Spring實現的可以指定entry引用級別的ConcurrentHashMap,默認的引用級別是soft,可以防止OOM results = softClassCache.get(beanClass); if (results != null) { return results; } results = new CachedIntrospectionResults(beanClass); ConcurrentMap , CachedIntrospectionResults> classCacheToUse; // isCacheSafe方法檢查給定的beanClass是否由入參中的classloader或者此classloader的祖先加載的(雙親委派的原理) // isClassLoaderAccepted檢查加載beanClass的classloader是否在可以接受的classloader的集合中 或者是集合中classloader的祖先 if (ClassUtils.isCacheSafe(beanClass, CachedIntrospectionResults.class.getClassLoader()) || isClassLoaderAccepted(beanClass.getClassLoader())) { classCacheToUse = strongClassCache; } else { if (logger.isDebugEnabled()) { logger.debug("Not strongly caching class [" + beanClass.getName() + "] because it is not cache-safe"); } classCacheToUse = softClassCache; } // 根據classloader的結果,將類信息加載到對應的緩存中 CachedIntrospectionResults existing = classCacheToUse.putIfAbsent(beanClass, results); return (existing != null ? existing : results); }
isAssignable源碼:
public static boolean isAssignable(Class> lhsType, Class> rhsType) { Assert.notNull(lhsType, "Left-hand side type must not be null"); Assert.notNull(rhsType, "Right-hand side type must not be null"); // 若左邊類型 是右邊類型的父類、父接口,或者左邊類型等于右邊類型 if (lhsType.isAssignableFrom(rhsType)) { return true; } // 左邊入參是否是基本類型 if (lhsType.isPrimitive()) { //primitiveWrapperTypeMap是從包裝類型到基本類型的map,將右邊入參轉化為基本類型 Class> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType); if (lhsType == resolvedPrimitive) { return true; } } else { // 將右邊入參轉化為包裝類型 Class> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) { return true; } } return false; }
ClassUtils.isAssignable()方法擴展了Class的isAssignableFrom()方法,即將Java的基本類型和包裝類型做了兼容。
總結一個看似簡單的BeanUtils工具類,其實里面包含的Java基礎的知識點非常多,包括類型信息、反射、線程安全、引用類型、類加載器等。Spring的BeanUtils的實現里使用了ConcurrentHashMap作為緩存,每次去獲取PropertyDescriptor時,可以直接去緩存里面獲取,而不必每次都去調用native方法,所以Spring的BeanUtils的性能還是很不錯的。
原文鏈接https://segmentfault.com/a/11...
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/71118.html
摘要:于是我建議這位小伙伴使用了進行屬性拷貝,這為我們的程序挖了一個坑阿里代碼規約當我們開啟阿里代碼掃描插件時,如果你使用了進行屬性拷貝,它會給你一個非常嚴重的警告。大名鼎鼎的提供的包,居然會存在性能問題,以致于阿里給出了嚴重的警告。 聲明:本文屬原創文章,始發于公號:程序員自學之道,并同步發布于 https://blog.csdn.net/dadiyang,特此,同步發布到 sf,轉載請注...
摘要:不同與其它中間件框架,中有大量的業務代碼,它向我們展示了大神是如何寫業務代碼的依賴的層次結構,如何進行基礎包配置,以及工具類編寫,可以稱之為之最佳實踐。代碼參考視圖解析器,這里的配置指的是不檢查頭,而且默認請求為格式。 不同與其它中間件框架,Apollo中有大量的業務代碼,它向我們展示了大神是如何寫業務代碼的:maven依賴的層次結構,如何進行基礎包配置,以及工具類編寫,可以稱之為sp...
摘要:拷貝操作又一個非常好用的工具類和中分別存在一個,提供了對。除了支持基本類型以及基本類型的數組之外,還支持這些類的對象,其余一概不支持。而且,由于這些類都是采用反射機制實現的,對程序的效率也會有影響。因此,慎用或者使用看效果如何 java bean拷貝操作又一個非常好用的工具類 BeanUitls :spring (org.springframework.beans.BeanUtils)...
摘要:眾所周知,類上面帶有注解的類,即為的啟動類。一個項目只能有一個啟動類。根據是否是環境創建默認的,通過掃描所有注解類來加載和最后通過實例化上下文對象,并返回。 ??眾所周知,類上面帶有@SpringBootApplication注解的類,即為springboot的啟動類。一個springboot項目只能有一個啟動類。我們來分析一下SpringBoot項目的啟動過程,首先看看啟動類里面都包...
摘要:以下內容基于如果你使用的也是相同的技術棧可以繼續往下閱讀,如果不是可以當作參考。編寫的四種方式裸寫最簡單最粗暴也是使用最多的一種方式,在寫的多了之后可以用生成工具生成。 導讀 在目前接觸過的項目中大多數的項目都會涉及到: crud相關的操作, 哪如何優雅的編寫crud操作呢?帶著這個問題,我們發現項目中大量的操作多是 創建實體 、刪除實例、 修改實體、 查詢單個實體、 分頁查詢多個實體...
閱讀 2422·2021-11-25 09:43
閱讀 1202·2021-09-07 10:16
閱讀 2616·2021-08-20 09:38
閱讀 2943·2019-08-30 15:55
閱讀 1462·2019-08-30 13:21
閱讀 894·2019-08-29 15:37
閱讀 1446·2019-08-27 10:56
閱讀 2097·2019-08-26 13:45