那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

Lombok注解引發的空指針問題分析

京東云 ? 來源:jf_75140285 ? 作者:jf_75140285 ? 2024-06-23 09:30 ? 次閱讀

一、問題描述

在一次上線后,日志中出現空指針的報錯,但是報錯代碼位置以及相應工具類未進行過修改,接下來進一步分析。

以下為報錯堆棧信息

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]
	
	..省略

?

二、問題分析

1.分析鏈路長,直接拋結論

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

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

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

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

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依賴Introspector的writeMethod對目標類賦值的工具,在轉換使用了@Accessors(chain = true)注解的類時,在獲取屬性描述PropertyDescriptor就不會返回這個屬性的writeMethod屬性,就相當于該類的屬性沒有“寫入方法”,這就造成了拷貝對象過程中出現空指針問題。

2.分析路徑

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

(1)通過報錯信息定位到代碼端,通常情況看到mtProcessDtoList是從服務中獲取,第一印象認為對象是可能為null,其實不然,仔細看堆棧,問題還是出在工具類里,

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

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這個代碼的實現,工具類的封裝的底層能力是BeanCopier提供的,從傳參來看并沒有我們常見的傳null后對null進行操作引起的空指針,還需要對BeanCopier的源碼進行分析。

public static void copyProperties(Object source, Object target) {
    if(source == null || target == null) {
        log.error("對象屬性COPY時入參為空,source:{},target:{}",JSON.toJSONString(source), JSON.toJSONString(target));
            return;
    }
    if(source instanceof List && target instanceof List) {
            throw new ParamErrorException("請使用[copyProperties(a,b,c)]方法進行集合類的值拷貝");
    }
    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是進行反編譯的,堆棧里提供的代碼行數已經失真了,直接貼上報空指針的源碼截圖。

wKgZomZ1Sj2AawKWAAD5rKum-TU315.png

wKgaomZ1Sj6AKumhAABEPBnfFNY119.png

getMethodInfo入參member是null,從而導致空指針。需要通過斷點跟蹤運行時的變量值,找到setters數組中的元素是如何生成的。

wKgZomZ1Sj-AbuHuAAERLXvJtiY604.png

(4)target是作為對象拷貝的目標對象的類,setters這個數組就是通過反射獲取該目標類的所有具備讀方法的描述對象(PropertyDescriptor對象,可以理解為屬性/方法描述)。這里面方法名有些歧義,不是說只返回getter相關的屬性對象,返回的是該類所有具備讀或寫方法的屬性描述,兩個布爾值的類型分別控制校驗讀或寫。

wKgaomZ1SkCAU8FJAACDyfFdSJ8253.png

wKgaomZ1SkGACIKDAAEFAfAY5cQ252.png

綜上,由于無法獲取目標類的writeMethod,從而沒有辦法找到這個屬性的寫入方法,就沒有辦法對目標對象繼續賦值。

wKgZomZ1SkKAVdZTAACqCq6knjc244.png

此時方向就轉到了目標類的實現上,分析到這里就跟Lombok產生了聯系。此處確實被修改過,WaybillProcess類增加了@Accessors這個注解。

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

(5)WaybillProcess使用了@Accessors(chain = true)這個注解,這就回到了開頭提到的,在使用了這個注解后該類生成的set方法返回值就不是void而是this,在通過Introspector獲取屬性描述時就不會被認定是寫入方法,在去掉這個注解后,writeMethodName就有值了。

wKgaomZ1SkOAa-dEAADFcU61du8872.png

三、解決辦法

解決辦法1:刪除該注解,將工程里鏈式set改成了常規的set賦值方式。

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

四、總結

凡是依賴JDK Introspector獲取類set方法描述的工具類、組件都會受到其寫入方法定義導致的一些列問題,目前在工程實踐中遇到了BeanCopier進行對象拷貝、BeanUtils對屬性進行賦值都會遇到問題。所以大家在日常開發過程中,如果該類已經被大面積的使用,在使用組件特性時需要多留意。

對于對象拷貝已經有很多最佳實踐了,有相關的文章大家可以推薦一下。

感謝閱讀!

審核編輯 黃宇

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 指針
    +關注

    關注

    1

    文章

    481

    瀏覽量

    70611
  • JDK
    JDK
    +關注

    關注

    0

    文章

    82

    瀏覽量

    16637
收藏 人收藏

    評論

    相關推薦

    C語言中空指針和野指針的概念及產生原因

    在C語言中,指針是一種非常強大和靈活的工具,但同時也容易引發一些問題,其中包括指針和野指針
    發表于 08-16 16:18 ?1539次閱讀

    如何有效的處理指針異常

    在編寫 Java 程序的過程中,有一種異常幾乎每個開發者都會遇到——指針異常( NullPointerException )。這個問題可能會讓一些新手菜鳥感到困擾,甚至一些經驗豐富的開發者也會不時
    的頭像 發表于 09-30 10:25 ?1591次閱讀

    指針指針的兩個小點

    大家都知道指針的學習對于c語言學習來說可謂是至關重要的,下面我們來說一下在指針中兩種比較特殊的關于指針的概念,野指針
    發表于 10-14 15:56

    函數指針的問題

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

    【設計技巧】指針的使用注意事項:指針指針賦值、void *指針

    前面的文章,分析指針的一些概念,可以說指針是C的靈魂,看起來簡單,但是想要理解透徹卻是相當難,需要大量的練習,不斷的鞏固,不斷的重復才能盡可能的理解指針,這里做一個簡單的階段總結。
    發表于 08-20 08:30

    為什么程序中會出現指針?

    為什么程序中會出現指針
    發表于 10-10 07:25

    指針引用缺陷分類假陽性識別方法

    針對靜態測試中空指針引用缺陷假陽性問題,提出一種指針引用缺陷分類假陽性識別方法。挖掘指針引用缺陷知識,對空
    發表于 11-25 11:04 ?8次下載
    <b class='flag-5'>空</b><b class='flag-5'>指針</b>引用缺陷分類假陽性識別方法

    Lombok開發插件使用小技巧

    0x01:Lombok簡介 Lombok 是一款 Java開發插件,使得 Java 開發者可以通過其定義的一些注解來消除業務工程中冗長和繁瑣的代碼,尤其對于簡單的 Java 模型對象(POJO)。在
    的頭像 發表于 06-12 18:07 ?1807次閱讀

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

    ? https://www.ramostear.com/blog/2020/04/28/uk1860p8.html ? 如果您正在閱讀此文,想必您對Project Lombok已經有了一段時間的了解
    的頭像 發表于 10-28 11:29 ?1224次閱讀

    Lombok同時使用@Data和@Builder的一個必須要避開的巨坑

    問題背景 Lombok @Data和@Builder分別單獨分析用法 解決方法 方法一 方法二 Lombok原理 總結 問題背景 Lombok使? 同時使?@Data和@Builder
    的頭像 發表于 10-11 18:14 ?2056次閱讀

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

    什么是注解? 當我們開發SpringBoot項目,我們只需對啟動類加上`@SpringBootApplication`,就能自動裝配,不需要編寫冗余的xml配置。當我們為項目添加lombok
    的頭像 發表于 02-09 14:18 ?800次閱讀
    Java<b class='flag-5'>注解</b>及其底層原理解析 1

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

    什么是注解? 當我們開發SpringBoot項目,我們只需對啟動類加上`@SpringBootApplication`,就能自動裝配,不需要編寫冗余的xml配置。當我們為項目添加lombok
    的頭像 發表于 02-09 14:18 ?542次閱讀
    Java<b class='flag-5'>注解</b>及其底層原理解析2

    Lombok的使用

    在平時我們工作的時候,我們經常會使用 toString() 方法來輸出一個對象的一些屬性信息。Lombok 給我們提供了一個自動生成 toString() 代碼的注解,可以減少代碼行數,如果代碼屬性
    的頭像 發表于 09-25 14:03 ?851次閱讀

    bigdecimal轉string類型避免指針

    指針異常的發生。本文將詳細介紹如何將BigDecimal對象轉換為String類型,以及如何避免指針異常。 首先,請確保在將BigDecimal對象轉換為String類型之前進行
    的頭像 發表于 11-30 11:12 ?2796次閱讀

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

    指針被釋放后,是不是就變成了指針?有好多同學提出了這樣的問題。 借用《C專家編程》上面的一段代碼,可以很好的解釋這個問題。 ? ? #include int main(){ char *s
    的頭像 發表于 01-22 09:23 ?75次閱讀
    百家乐官网最好的投注方法| 聚众玩百家乐官网的玩法技巧和规则 | 明升投注网 | 百家乐官网开户送8彩金| 澳门百家乐打法精华| 炸金花棋牌游戏| 百家乐官网和局投注法| 百家乐真人游戏娱乐平台| 三江| 江山百家乐官网的玩法技巧和规则| 乐天百家乐的玩法技巧和规则 | 百家乐电子游戏试| 国际百家乐官网规则| 打百家乐的技巧| bet365提款| 百家乐官网娱乐网网77scs| 太阳城投诉| 百家乐官网庄闲预测| 威尼斯人娱乐城网络博彩| 金博士百家乐官网娱乐城| 宝马百家乐的玩法技巧和规则| 百家乐官网长路投注法| 百家乐7杀6| 澳门百家乐官网博彩网| 百家乐赌博论坛| 百家乐官网有方法赚反水| 百家乐开户送18元| 百家乐详解| 金都百家乐现金网| 崇义县| 最好的百家乐博彩网站| 丰禾娱乐| 百家乐网上投注作弊| 澳门百家乐官网上下限| 百家乐合| 百家乐官网制胜法| 雁荡棋牌游戏| 真人百家乐信誉| 博九娱乐城| 百家乐网址哪里有| 百家乐官网tt娱乐城|