一区二区三区三上|欧美在线视频五区|国产午夜无码在线观看视频|亚洲国产裸体网站|无码成年人影视|亚洲AV亚洲AV|成人开心激情五月|欧美性爱内射视频|超碰人人干人人上|一区二区无码三区亚洲人区久久精品

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Lombok注解引發(fā)的空指針問(wèn)題分析

京東云 ? 來(lái)源:jf_75140285 ? 作者:jf_75140285 ? 2024-06-23 09:30 ? 次閱讀
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

一、問(wèn)題描述

在一次上線后,日志中出現(xiàn)空指針的報(bào)錯(cuò),但是報(bào)錯(cuò)代碼位置以及相應(yīng)工具類(lèi)未進(jìn)行過(guò)修改,接下來(lái)進(jìn)一步分析。

以下為報(bào)錯(cuò)堆棧信息:

java.lang.NullPointerException: null
	at net.sf.cglib.core.ReflectUtils.getMethodInfo(ReflectUtils.java:424) ~[cglib-3.1.jar:?]
	at net.sf.cglib.beans.BeanCopier$Generator.generateClass(BeanCopier.java:133) ~[cglib-3.1.jar:?]
	at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25) ~[cglib-3.1.jar:?]
	at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:216) ~[cglib-3.1.jar:?]
	at net.sf.cglib.beans.BeanCopier$Generator.create(BeanCopier.java:90) ~[cglib-3.1.jar:?]
	at net.sf.cglib.beans.BeanCopier.create(BeanCopier.java:50) ~[cglib-3.1.jar:?]
	at ***.CglibBeanCopier.copyProperties(CglibBeanCopier.java:90) ~[***.jar:1.2.0]
	at ***.CglibBeanCopier.copyProperties(CglibBeanCopier.java:113) ~[***.jar:1.2.0]
	at ***.CglibBeanCopier.copyPropertiesOfList(CglibBeanCopier.java:123) ~[***.jar:1.2.0]
	
	..省略

?

二、問(wèn)題分析

1.分析鏈路長(zhǎng),直接拋結(jié)論

通過(guò)Lombok提供的功能使得我們不必在對(duì)象中顯式定義get和set方法。并且Lombok提供鏈?zhǔn)?a target="_blank">編程,通過(guò)在對(duì)象頭部加上@Accessors(chain = true)注解,給屬性賦值時(shí),可以寫(xiě)成obj.setA(a).setB(b).setC(c),省去先new再對(duì)屬性逐個(gè)set賦值。使用了該注解,這個(gè)類(lèi)的set方法返回我就不是void而是this對(duì)象本身。

@Accessors(chain = true)
public class YourClass {
    private int a;

    @Setter
    public YourClass setA(int a) {
        this.a = a;
        return this;
    }
}

而JDK Introspector(它為目標(biāo)JavaBean提供了一種了解原類(lèi)方法、屬性和事件的標(biāo)準(zhǔn)方法)中對(duì)寫(xiě)入方法是有特殊判斷的,截取Introspector.getBeanInfo(beanClass)中一段源碼,只有返回值是void,且方法名以set作為前綴的,才會(huì)被當(dāng)做writeMethod,即寫(xiě)入方法。所以返回值為void且是“set”開(kāi)頭的才是Introspector認(rèn)為的寫(xiě)入方法,一種狹義的定義。

else if (argCount == 1) {
   if (int.class.equals(argTypes[0]) && name.startsWith(GET_PREFIX)) {
      pd = new IndexedPropertyDescriptor(this.beanClass, name.substring(3), null, null, method, null);
   } else if (void.class.equals(resultType) && name.startsWith(SET_PREFIX)) {
      // Simple setter
      pd = new PropertyDescriptor(this.beanClass, name.substring(3), null, method);
      if (throwsException(method, PropertyVetoException.class)) {
         pd.setConstrained(true);
      }
   }
}

像BeanCopier依賴(lài)Introspector的writeMethod對(duì)目標(biāo)類(lèi)賦值的工具,在轉(zhuǎn)換使用了@Accessors(chain = true)注解的類(lèi)時(shí),在獲取屬性描述PropertyDescriptor就不會(huì)返回這個(gè)屬性的writeMethod屬性,就相當(dāng)于該類(lèi)的屬性沒(méi)有“寫(xiě)入方法”,這就造成了拷貝對(duì)象過(guò)程中出現(xiàn)空指針問(wèn)題。

2.分析路徑

List mtProcessDtoList = **WaybillProvider.getMtWayBillProcess(**);
List mtProcessList = CglibBeanCopier.copyPropertiesOfList(mtProcessDtoList, WaybillProcess.class);
if(CollectionUtils.isNotEmpty(mtProcessList)) {
   waybillProcessList.addAll(mtProcessList);
}

(1)通過(guò)報(bào)錯(cuò)信息定位到代碼端,通常情況看到mtProcessDtoList是從服務(wù)中獲取,第一印象認(rèn)為對(duì)象是可能為null,其實(shí)不然,仔細(xì)看堆棧,問(wèn)題還是出在工具類(lèi)里,

“***.CglibBeanCopier.copyProperties”,繼續(xù)看這段代碼是存在判空操作的,造成空指針的還是copyProperties這個(gè)方法。

public static  List copyPropertiesOfList(List sourceList, Class targetClass) {
    if (sourceList == null || sourceList.isEmpty()) {
        return Collections.emptyList();
    }
    List resultList = new ArrayList(sourceList.size());
    for (Object o : sourceList) {
        resultList.add(copyProperties(o, targetClass));
    }
    return resultList;
}

(2)具體看copyProperties這個(gè)代碼的實(shí)現(xiàn),工具類(lèi)的封裝的底層能力是BeanCopier提供的,從傳參來(lái)看并沒(méi)有我們常見(jiàn)的傳null后對(duì)null進(jìn)行操作引起的空指針,還需要對(duì)BeanCopier的源碼進(jìn)行分析。

public static void copyProperties(Object source, Object target) {
    if(source == null || target == null) {
        log.error("對(duì)象屬性COPY時(shí)入?yún)榭?source:{},target:{}",JSON.toJSONString(source), JSON.toJSONString(target));
            return;
    }
    if(source instanceof List && target instanceof List) {
            throw new ParamErrorException("請(qǐng)使用[copyProperties(a,b,c)]方法進(jìn)行集合類(lèi)的值拷貝");
    }
    String beanKey = generateKey(source.getClass(), target.getClass());
    BeanCopier copier;
    if (! beanCopierMap.containsKey(beanKey)) {
        copier = BeanCopier.create(source.getClass(), target.getClass(), false);
        beanCopierMap.put(beanKey, copier);
     } else {
        copier = beanCopierMap.get(beanKey);
     }
     copier.copy(source, target, null);
}

(3)由于jar是進(jìn)行反編譯的,堆棧里提供的代碼行數(shù)已經(jīng)失真了,直接貼上報(bào)空指針的源碼截圖。

wKgZomZ1Sj2AawKWAAD5rKum-TU315.png

wKgaomZ1Sj6AKumhAABEPBnfFNY119.png

getMethodInfo入?yún)ember是null,從而導(dǎo)致空指針。需要通過(guò)斷點(diǎn)跟蹤運(yùn)行時(shí)的變量值,找到setters數(shù)組中的元素是如何生成的。

wKgZomZ1Sj-AbuHuAAERLXvJtiY604.png

(4)target是作為對(duì)象拷貝的目標(biāo)對(duì)象的類(lèi),setters這個(gè)數(shù)組就是通過(guò)反射獲取該目標(biāo)類(lèi)的所有具備讀方法的描述對(duì)象(PropertyDescriptor對(duì)象,可以理解為屬性/方法描述)。這里面方法名有些歧義,不是說(shuō)只返回getter相關(guān)的屬性對(duì)象,返回的是該類(lèi)所有具備讀或?qū)懛椒ǖ膶傩悦枋?,兩個(gè)布爾值的類(lèi)型分別控制校驗(yàn)讀或?qū)憽?/p>

wKgaomZ1SkCAU8FJAACDyfFdSJ8253.png

wKgaomZ1SkGACIKDAAEFAfAY5cQ252.png

綜上,由于無(wú)法獲取目標(biāo)類(lèi)的writeMethod,從而沒(méi)有辦法找到這個(gè)屬性的寫(xiě)入方法,就沒(méi)有辦法對(duì)目標(biāo)對(duì)象繼續(xù)賦值。

wKgZomZ1SkKAVdZTAACqCq6knjc244.png

此時(shí)方向就轉(zhuǎn)到了目標(biāo)類(lèi)的實(shí)現(xiàn)上,分析到這里就跟Lombok產(chǎn)生了聯(lián)系。此處確實(shí)被修改過(guò),WaybillProcess類(lèi)增加了@Accessors這個(gè)注解。

@Setter
@Getter
@Accessors(chain = true)
public class WaybillProcess {}

(5)WaybillProcess使用了@Accessors(chain = true)這個(gè)注解,這就回到了開(kāi)頭提到的,在使用了這個(gè)注解后該類(lèi)生成的set方法返回值就不是void而是this,在通過(guò)Introspector獲取屬性描述時(shí)就不會(huì)被認(rèn)定是寫(xiě)入方法,在去掉這個(gè)注解后,writeMethodName就有值了。

wKgaomZ1SkOAa-dEAADFcU61du8872.png

三、解決辦法

解決辦法1:刪除該注解,將工程里鏈?zhǔn)絪et改成了常規(guī)的set賦值方式。

解決辦法2:保留該注解,替換對(duì)象拷貝的工具類(lèi),建議使用MapStruct配合Lombok,直接在編譯時(shí)生成get/set方法,更加安全,功能也更加強(qiáng)大。

四、總結(jié)

凡是依賴(lài)JDK Introspector獲取類(lèi)set方法描述的工具類(lèi)、組件都會(huì)受到其寫(xiě)入方法定義導(dǎo)致的一些列問(wèn)題,目前在工程實(shí)踐中遇到了BeanCopier進(jìn)行對(duì)象拷貝、BeanUtils對(duì)屬性進(jìn)行賦值都會(huì)遇到問(wèn)題。所以大家在日常開(kāi)發(fā)過(guò)程中,如果該類(lèi)已經(jīng)被大面積的使用,在使用組件特性時(shí)需要多留意。

對(duì)于對(duì)象拷貝已經(jīng)有很多最佳實(shí)踐了,有相關(guān)的文章大家可以推薦一下。

感謝閱讀!

審核編輯 黃宇

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 指針
    +關(guān)注

    關(guān)注

    1

    文章

    484

    瀏覽量

    71097
  • JDK
    JDK
    +關(guān)注

    關(guān)注

    0

    文章

    83

    瀏覽量

    16851
收藏 人收藏
加入交流群
微信小助手二維碼

掃碼添加小助手

加入工程師交流群

    評(píng)論

    相關(guān)推薦
    熱點(diǎn)推薦

    C語(yǔ)言中空指針和野指針的概念及產(chǎn)生原因

    在C語(yǔ)言中,指針是一種非常強(qiáng)大和靈活的工具,但同時(shí)也容易引發(fā)一些問(wèn)題,其中包括指針和野指針。
    發(fā)表于 08-16 16:18 ?1922次閱讀

    如何有效的處理指針異常

    在編寫(xiě) Java 程序的過(guò)程中,有一種異常幾乎每個(gè)開(kāi)發(fā)者都會(huì)遇到——指針異常( NullPointerException )。這個(gè)問(wèn)題可能會(huì)讓一些新手菜鳥(niǎo)感到困擾,甚至一些經(jīng)驗(yàn)豐富的開(kāi)發(fā)者也會(huì)不時(shí)
    的頭像 發(fā)表于 09-30 10:25 ?1929次閱讀

    指針指針的兩個(gè)小點(diǎn)

    大家都知道指針的學(xué)習(xí)對(duì)于c語(yǔ)言學(xué)習(xí)來(lái)說(shuō)可謂是至關(guān)重要的,下面我們來(lái)說(shuō)一下在指針中兩種比較特殊的關(guān)于指針的概念,野指針
    發(fā)表于 10-14 15:56

    函數(shù)指針的問(wèn)題

    您好。我把函數(shù)指針作為參數(shù)傳遞給函數(shù)時(shí)遇到了一些問(wèn)題。問(wèn)題基本上是在一些循環(huán)下,函數(shù)指針的。最后檢查代碼和注釋?zhuān)?):(1)這是關(guān)鍵。如果我不使用這個(gè)句子,“數(shù)據(jù)”指針總是
    發(fā)表于 08-24 15:49

    【設(shè)計(jì)技巧】指針的使用注意事項(xiàng):指針指針賦值、void *指針

    前面的文章,分析指針的一些概念,可以說(shuō)指針是C的靈魂,看起來(lái)簡(jiǎn)單,但是想要理解透徹卻是相當(dāng)難,需要大量的練習(xí),不斷的鞏固,不斷的重復(fù)才能盡可能的理解指針,這里做一個(gè)簡(jiǎn)單的階段總結(jié)。
    發(fā)表于 08-20 08:30

    為什么程序中會(huì)出現(xiàn)指針?

    為什么程序中會(huì)出現(xiàn)指針
    發(fā)表于 10-10 07:25

    指針引用缺陷分類(lèi)假陽(yáng)性識(shí)別方法

    針對(duì)靜態(tài)測(cè)試中空指針引用缺陷假陽(yáng)性問(wèn)題,提出一種指針引用缺陷分類(lèi)假陽(yáng)性識(shí)別方法。挖掘指針引用缺陷知識(shí),對(duì)空
    發(fā)表于 11-25 11:04 ?8次下載
    <b class='flag-5'>空</b><b class='flag-5'>指針</b>引用缺陷分類(lèi)假陽(yáng)性識(shí)別方法

    Lombok開(kāi)發(fā)插件使用小技巧

    0x01:Lombok簡(jiǎn)介 Lombok 是一款 Java開(kāi)發(fā)插件,使得 Java 開(kāi)發(fā)者可以通過(guò)其定義的一些注解來(lái)消除業(yè)務(wù)工程中冗長(zhǎng)和繁瑣的代碼,尤其對(duì)于簡(jiǎn)單的 Java 模型對(duì)象(POJO)。在
    的頭像 發(fā)表于 06-12 18:07 ?2022次閱讀

    重演自己如何掉入Lombok的戲法陷阱

    ? https://www.ramostear.com/blog/2020/04/28/uk1860p8.html ? 如果您正在閱讀此文,想必您對(duì)Project Lombok已經(jīng)有了一段時(shí)間的了解
    的頭像 發(fā)表于 10-28 11:29 ?1300次閱讀

    Lombok同時(shí)使用@Data和@Builder的一個(gè)必須要避開(kāi)的巨坑

    問(wèn)題背景 Lombok @Data和@Builder分別單獨(dú)分析用法 解決方法 方法一 方法二 Lombok原理 總結(jié) 問(wèn)題背景 Lombok使? 同時(shí)使?@Data和@Builder
    的頭像 發(fā)表于 10-11 18:14 ?2334次閱讀

    Java注解及其底層原理解析 1

    什么是注解? 當(dāng)我們開(kāi)發(fā)SpringBoot項(xiàng)目,我們只需對(duì)啟動(dòng)類(lèi)加上`@SpringBootApplication`,就能自動(dòng)裝配,不需要編寫(xiě)冗余的xml配置。當(dāng)我們?yōu)轫?xiàng)目添加lombok
    的頭像 發(fā)表于 02-09 14:18 ?966次閱讀
    Java<b class='flag-5'>注解</b>及其底層原理解析 1

    Java注解及其底層原理解析2

    什么是注解? 當(dāng)我們開(kāi)發(fā)SpringBoot項(xiàng)目,我們只需對(duì)啟動(dòng)類(lèi)加上`@SpringBootApplication`,就能自動(dòng)裝配,不需要編寫(xiě)冗余的xml配置。當(dāng)我們?yōu)轫?xiàng)目添加lombok
    的頭像 發(fā)表于 02-09 14:18 ?683次閱讀
    Java<b class='flag-5'>注解</b>及其底層原理解析2

    Lombok的使用

    在平時(shí)我們工作的時(shí)候,我們經(jīng)常會(huì)使用 toString() 方法來(lái)輸出一個(gè)對(duì)象的一些屬性信息。Lombok 給我們提供了一個(gè)自動(dòng)生成 toString() 代碼的注解,可以減少代碼行數(shù),如果代碼屬性
    的頭像 發(fā)表于 09-25 14:03 ?1148次閱讀

    bigdecimal轉(zhuǎn)string類(lèi)型避免指針

    指針異常的發(fā)生。本文將詳細(xì)介紹如何將BigDecimal對(duì)象轉(zhuǎn)換為String類(lèi)型,以及如何避免指針異常。 首先,請(qǐng)確保在將BigDecimal對(duì)象轉(zhuǎn)換為String類(lèi)型之前進(jìn)行
    的頭像 發(fā)表于 11-30 11:12 ?3323次閱讀

    指針被釋放后就變成了指針

    指針被釋放后,是不是就變成了指針?有好多同學(xué)提出了這樣的問(wèn)題。 借用《C專(zhuān)家編程》上面的一段代碼,可以很好的解釋這個(gè)問(wèn)題。 ? ? #include int main(){ char *s
    的頭像 發(fā)表于 01-22 09:23 ?363次閱讀