摘要:前置知識(shí)在分析源碼前,我們先溫習(xí)一下以下的知識(shí)點(diǎn)。類在中萬物皆對(duì)象,而且我們?cè)诖a中寫的每一個(gè)類也都是對(duì)象,是類的對(duì)象。總結(jié)一個(gè)看似簡(jiǎn)單的工具類,其實(shí)里面包含的基礎(chǔ)的知識(shí)點(diǎn)非常多,包括類型信息反射線程安全引用類型類加載器等。
背景
在我們著手一個(gè)Java Web項(xiàng)目的時(shí)候,經(jīng)常會(huì)遇到DO、VO、DTO對(duì)象之間的屬性拷貝,若采用get、set的方法來進(jìn)行賦值的話,代碼會(huì)相當(dāng)冗長(zhǎng)丑陋,一般我們會(huì)采用Spring的BeanUtils類來進(jìn)行屬性拷貝,其基本原理就是通過Java的反射機(jī)制,下面我們來看一下源碼的具體實(shí)現(xiàn)。
前置知識(shí)在分析源碼前,我們先溫習(xí)一下以下的知識(shí)點(diǎn)。
java.lang.Class類在Java中萬物皆對(duì)象,而且我們?cè)诖a中寫的每一個(gè)類也都是對(duì)象,是java.lang.Class類的對(duì)象。所以,每個(gè)類都有自己的實(shí)例對(duì)象,而且它們自己也都是Class類的對(duì)象。
我們來看一下Class類的構(gòu)造方法:
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類的構(gòu)造方法是私有的,只有JVM可以創(chuàng)建該類的對(duì)象,因此我們無法在代碼中通過new的方式顯示聲明一個(gè)Class對(duì)象。
但是,我們依然有其他方式獲得Class類的對(duì)象:
1.通過類的靜態(tài)成員變量
Class clazz = Test.class;
2.通過對(duì)象的getClass()方法
Class clazz = test.getClass();
3.通過Class的靜態(tài)方法forName()
// forName需要傳入類的全路徑 Class clazz = Class.forName("destiny.iron.api.model.Test");基本類型和包裝類型
基本類型和其對(duì)應(yīng)的包裝類的Class對(duì)象是不相等的,即long.class != Long.class 。
PropertyDescriptor類PropertyDescriptor類表示的是標(biāo)準(zhǔn)形式的Java Bean通過存取器(即get set方法)導(dǎo)出的一個(gè)屬性,比如,我們可以通過以下方式,對(duì)對(duì)象的屬性進(jìn)行賦值:
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方法對(duì)應(yīng)的Read方法 setMethod.invoke(test1, "bbbbb"); System.out.print(test1); } }引用類型
Java中有strong、soft、weak、phantom四種引用類型,下面介紹一下soft引用和weak引用:
Soft Reference: 當(dāng)對(duì)象是Soft reference可達(dá)時(shí),向系統(tǒng)申請(qǐng)更多內(nèi)存,GC不是直接回收它,而是當(dāng)內(nèi)存不足的時(shí)候才回收它。因此Soft reference適合用于構(gòu)建一些緩存系統(tǒng)。
Weak Reference: 弱引用的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次GC發(fā)生之前。當(dāng)垃圾收集器工作時(shí),無論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。
源碼分析private static void copyProperties(Object source, Object target, Class> editable, String... ignoreProperties) throws BeansException { // 檢查source和target對(duì)象是否為null,否則拋運(yùn)行時(shí)異常 Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"); // 獲取target對(duì)象的類信息 Class> actualEditable = target.getClass(); // 若editable不為null,檢查target對(duì)象是否是editable類的實(shí)例,若不是則拋出運(yùn)行時(shí)異常 // 這里的editable類是為了做屬性拷貝時(shí)限制用的 // 若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; } // 獲取目標(biāo)類的所有PropertyDescriptor,getPropertyDescriptors這個(gè)方法請(qǐng)看下方 PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); ListignoreList = (ignoreProperties != null ? Arrays.asList(ignoreProperties) : null); for (PropertyDescriptor targetPd : targetPds) { // 獲取該屬性對(duì)應(yīng)的set方法 Method writeMethod = targetPd.getWriteMethod(); // 屬性的set方法存在 且 該屬性不包含在忽略屬性列表中 if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { // 獲取source類相同名字的PropertyDescriptor, getPropertyDescriptor的具體實(shí)現(xiàn)看下方 PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null) { // 獲取對(duì)應(yīng)的get方法 Method readMethod = sourcePd.getReadMethod(); // set方法存在 且 target的set方法的入?yún)⑹莝ource的get方法返回值的父類或父接口或者類型相同 // 具體ClassUtils.isAssignable()的實(shí)現(xiàn)方式請(qǐng)看下面詳解 if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { try { //get方法是否是public的 if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { //暴力反射,取消權(quán)限控制檢查 readMethod.setAccessible(true); } //獲取get方法的返回值 Object value = readMethod.invoke(source); // 原理同上 if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } // 將get方法的返回值 賦值給set方法作為入?yún)? 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類是對(duì)PropertyDescriptor的一個(gè)封裝實(shí)現(xiàn),看forClass方法的實(shí)現(xiàn) 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作為實(shí)現(xiàn) CachedIntrospectionResults results = strongClassCache.get(beanClass); if (results != null) { return results; } // 若strongClassCache中不存在,則去softClassCache去獲取,softClassCache的聲明如下 // softClassCache = new ConcurrentReferenceHashMap , CachedIntrospectionResults>(64); // ConcurrentReferenceHashMap是Spring實(shí)現(xiàn)的可以指定entry引用級(jí)別的ConcurrentHashMap,默認(rèn)的引用級(jí)別是soft,可以防止OOM results = softClassCache.get(beanClass); if (results != null) { return results; } results = new CachedIntrospectionResults(beanClass); ConcurrentMap , CachedIntrospectionResults> classCacheToUse; // isCacheSafe方法檢查給定的beanClass是否由入?yún)⒅械腸lassloader或者此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; } // 根據(jù)classloader的結(jié)果,將類信息加載到對(duì)應(yīng)的緩存中 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; } // 左邊入?yún)⑹欠袷腔绢愋? if (lhsType.isPrimitive()) { //primitiveWrapperTypeMap是從包裝類型到基本類型的map,將右邊入?yún)⑥D(zhuǎn)化為基本類型 Class> resolvedPrimitive = primitiveWrapperTypeMap.get(rhsType); if (lhsType == resolvedPrimitive) { return true; } } else { // 將右邊入?yún)⑥D(zhuǎn)化為包裝類型 Class> resolvedWrapper = primitiveTypeToWrapperMap.get(rhsType); if (resolvedWrapper != null && lhsType.isAssignableFrom(resolvedWrapper)) { return true; } } return false; }
ClassUtils.isAssignable()方法擴(kuò)展了Class的isAssignableFrom()方法,即將Java的基本類型和包裝類型做了兼容。
總結(jié)一個(gè)看似簡(jiǎn)單的BeanUtils工具類,其實(shí)里面包含的Java基礎(chǔ)的知識(shí)點(diǎn)非常多,包括類型信息、反射、線程安全、引用類型、類加載器等。Spring的BeanUtils的實(shí)現(xiàn)里使用了ConcurrentHashMap作為緩存,每次去獲取PropertyDescriptor時(shí),可以直接去緩存里面獲取,而不必每次都去調(diào)用native方法,所以Spring的BeanUtils的性能還是很不錯(cuò)的。
原文鏈接https://segmentfault.com/a/11...
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/71118.html
摘要:于是我建議這位小伙伴使用了進(jìn)行屬性拷貝,這為我們的程序挖了一個(gè)坑阿里代碼規(guī)約當(dāng)我們開啟阿里代碼掃描插件時(shí),如果你使用了進(jìn)行屬性拷貝,它會(huì)給你一個(gè)非常嚴(yán)重的警告。大名鼎鼎的提供的包,居然會(huì)存在性能問題,以致于阿里給出了嚴(yán)重的警告。 聲明:本文屬原創(chuàng)文章,始發(fā)于公號(hào):程序員自學(xué)之道,并同步發(fā)布于 https://blog.csdn.net/dadiyang,特此,同步發(fā)布到 sf,轉(zhuǎn)載請(qǐng)注...
摘要:不同與其它中間件框架,中有大量的業(yè)務(wù)代碼,它向我們展示了大神是如何寫業(yè)務(wù)代碼的依賴的層次結(jié)構(gòu),如何進(jìn)行基礎(chǔ)包配置,以及工具類編寫,可以稱之為之最佳實(shí)踐。代碼參考視圖解析器,這里的配置指的是不檢查頭,而且默認(rèn)請(qǐng)求為格式。 不同與其它中間件框架,Apollo中有大量的業(yè)務(wù)代碼,它向我們展示了大神是如何寫業(yè)務(wù)代碼的:maven依賴的層次結(jié)構(gòu),如何進(jìn)行基礎(chǔ)包配置,以及工具類編寫,可以稱之為sp...
摘要:拷貝操作又一個(gè)非常好用的工具類和中分別存在一個(gè),提供了對(duì)。除了支持基本類型以及基本類型的數(shù)組之外,還支持這些類的對(duì)象,其余一概不支持。而且,由于這些類都是采用反射機(jī)制實(shí)現(xiàn)的,對(duì)程序的效率也會(huì)有影響。因此,慎用或者使用看效果如何 java bean拷貝操作又一個(gè)非常好用的工具類 BeanUitls :spring (org.springframework.beans.BeanUtils)...
摘要:眾所周知,類上面帶有注解的類,即為的啟動(dòng)類。一個(gè)項(xiàng)目只能有一個(gè)啟動(dòng)類。根據(jù)是否是環(huán)境創(chuàng)建默認(rèn)的,通過掃描所有注解類來加載和最后通過實(shí)例化上下文對(duì)象,并返回。 ??眾所周知,類上面帶有@SpringBootApplication注解的類,即為springboot的啟動(dòng)類。一個(gè)springboot項(xiàng)目只能有一個(gè)啟動(dòng)類。我們來分析一下SpringBoot項(xiàng)目的啟動(dòng)過程,首先看看啟動(dòng)類里面都包...
摘要:以下內(nèi)容基于如果你使用的也是相同的技術(shù)??梢岳^續(xù)往下閱讀,如果不是可以當(dāng)作參考。編寫的四種方式裸寫最簡(jiǎn)單最粗暴也是使用最多的一種方式,在寫的多了之后可以用生成工具生成。 導(dǎo)讀 在目前接觸過的項(xiàng)目中大多數(shù)的項(xiàng)目都會(huì)涉及到: crud相關(guān)的操作, 哪如何優(yōu)雅的編寫crud操作呢?帶著這個(gè)問題,我們發(fā)現(xiàn)項(xiàng)目中大量的操作多是 創(chuàng)建實(shí)體 、刪除實(shí)例、 修改實(shí)體、 查詢單個(gè)實(shí)體、 分頁查詢多個(gè)實(shí)體...
閱讀 2429·2021-11-25 09:43
閱讀 1206·2021-09-07 10:16
閱讀 2626·2021-08-20 09:38
閱讀 2948·2019-08-30 15:55
閱讀 1467·2019-08-30 13:21
閱讀 900·2019-08-29 15:37
閱讀 1451·2019-08-27 10:56
閱讀 2102·2019-08-26 13:45