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< WaybillProcessDTO > mtProcessDtoList = **WaybillProvider.getMtWayBillProcess(**);
List< WaybillProcess > mtProcessList = CglibBeanCopier.copyPropertiesOfList(mtProcessDtoList, WaybillProcess.class);
if(CollectionUtils.isNotEmpty(mtProcessList)) {
   waybillProcessList.addAll(mtProcessList);
}

(1)通过报错信息定位到代码端,通常情况看到mtProcessDtoList是从服务中获取,第一印象认为对象是可能为null,其实不然,仔细看堆栈,问题还是出在工具类里,

“***.CglibBeanCopier.copyProperties”,继续看这段代码是存在判空操作的,造成空指针的还是copyProperties这个方法。

public static < T > List< T > copyPropertiesOfList(List< ? > sourceList, Class< T > targetClass) {
    if (sourceList == null || sourceList.isEmpty()) {
        return Collections.emptyList();
    }
    List< T > 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

    文章

    475

    浏览量

    70457
  • JDK
    JDK
    +关注

    关注

    0

    文章

    80

    浏览量

    16548
收藏 人收藏

    评论

    相关推荐

    C语言中空指针和野指针的概念及产生原因

    在C语言中,指针是一种非常强大和灵活的工具,但同时也容易引发一些问题,其中包括指针和野指针
    发表于 08-16 16:18 966次阅读

    如何有效的处理指针异常

    在编写 Java 程序的过程中,有一种异常几乎每个开发者都会遇到——指针异常( NullPointerException )。这个问题可能会让一些新手菜鸟感到困扰,甚至一些经验丰富的开发者也会不时
    的头像 发表于 09-30 10:25 1338次阅读

    指针指针的两个小点

    大家都知道指针的学习对于c语言学习来说可谓是至关重要的,下面我们来说一下在指针中两种比较特殊的关于指针的概念,野指针
    发表于 10-14 15:56

    函数指针的问题

    您好。我把函数指针作为参数传递给函数时遇到了一些问题。问题基本上是在一些循环下,函数指针的。最后检查代码和注释(1):(1)这是关键。如果我不使用这个句子,“数据”指针总是
    发表于 08-24 15:49

    【设计技巧】指针的使用注意事项:指针指针赋值、void *指针

    前面的文章,分析指针的一些概念,可以说指针是C的灵魂,看起来简单,但是想要理解透彻却是相当难,需要大量的练习,不断的巩固,不断的重复才能尽可能的理解指针,这里做一个简单的阶段总结。
    发表于 08-20 08:30

    DevEco Studio打开历史工程报指针错误是为什么?

    DevEcoStudio打开历史工程报指针错误
    发表于 06-10 10:21

    为什么程序中会出现指针?

    为什么程序中会出现指针
    发表于 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 1687次阅读

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

      https://www.ramostear.com/blog/2020/04/28/uk1860p8.html   如果您正在阅读此文,想必您对Project Lombok已经有了一段时间的了解
    的头像 发表于 10-28 11:29 1152次阅读

    Lombok同时使用@Data和@Builder的一个必须要避开的巨坑

    问题背景 Lombok @Data和@Builder分别单独分析用法 解决方法 方法一 方法二 Lombok原理 总结 问题背景 Lombok使⽤ 同时使⽤@Data和@Builder
    的头像 发表于 10-11 18:14 1822次阅读

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

    什么是注解? 当我们开发SpringBoot项目,我们只需对启动类加上`@SpringBootApplication`,就能自动装配,不需要编写冗余的xml配置。当我们为项目添加lombok
    的头像 发表于 02-09 14:18 651次阅读
    Java<b class='flag-5'>注解</b>及其底层原理解析 1

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

    什么是注解? 当我们开发SpringBoot项目,我们只需对启动类加上`@SpringBootApplication`,就能自动装配,不需要编写冗余的xml配置。当我们为项目添加lombok
    的头像 发表于 02-09 14:18 424次阅读
    Java<b class='flag-5'>注解</b>及其底层原理解析2

    Lombok的使用

    在平时我们工作的时候,我们经常会使用 toString() 方法来输出一个对象的一些属性信息。Lombok 给我们提供了一个自动生成 toString() 代码的注解,可以减少代码行数,如果代码属性
    的头像 发表于 09-25 14:03 610次阅读

    bigdecimal转string类型避免指针

    指针异常的发生。本文将详细介绍如何将BigDecimal对象转换为String类型,以及如何避免指针异常。 首先,请确保在将BigDecimal对象转换为String类型之前进行
    的头像 发表于 11-30 11:12 2093次阅读