成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

Spring BeanUtils源碼分析

darkbug / 2535人閱讀

摘要:前置知識(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ì)采用SpringBeanUtils類來進(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);
        List ignoreList = (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)非常多,包括類型信息、反射、線程安全、引用類型、類加載器等。SpringBeanUtils的實(shí)現(xiàn)里使用了ConcurrentHashMap作為緩存,每次去獲取PropertyDescriptor時(shí),可以直接去緩存里面獲取,而不必每次都去調(diào)用native方法,所以SpringBeanUtils的性能還是很不錯(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

相關(guān)文章

  • 為什么阿里代碼規(guī)約要求避免使用 Apache BeanUtils 進(jìn)行屬性的拷貝

    摘要:于是我建議這位小伙伴使用了進(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)注...

    raledong 評(píng)論0 收藏0
  • Apollo源碼分析(二): Apollo的代碼層次

    摘要:不同與其它中間件框架,中有大量的業(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...

    cyqian 評(píng)論0 收藏0
  • BeanUtils工具使用細(xì)節(jié)

    摘要:拷貝操作又一個(gè)非常好用的工具類和中分別存在一個(gè),提供了對(duì)。除了支持基本類型以及基本類型的數(shù)組之外,還支持這些類的對(duì)象,其余一概不支持。而且,由于這些類都是采用反射機(jī)制實(shí)現(xiàn)的,對(duì)程序的效率也會(huì)有影響。因此,慎用或者使用看效果如何 java bean拷貝操作又一個(gè)非常好用的工具類 BeanUitls :spring (org.springframework.beans.BeanUtils)...

    afishhhhh 評(píng)論0 收藏0
  • springboot源碼分析系列(二)--SpringApplication.run()啟動(dòng)流程

    摘要:眾所周知,類上面帶有注解的類,即為的啟動(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)類里面都包...

    adie 評(píng)論0 收藏0
  • Spring Boot 中 crud如何優(yōu)雅的實(shí)現(xiàn)-附代碼

    摘要:以下內(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í)體...

    wing324 評(píng)論0 收藏0

發(fā)表評(píng)論

0條評(píng)論

最新活動(dòng)
閱讀需要支付1元查看
<