首页 > 其他分享 >Lombok注解引发的空指针问题分析

Lombok注解引发的空指针问题分析

时间:2024-06-21 17:35:17浏览次数:28  
标签:set java BeanCopier jar cglib 注解 Lombok null 指针

一、问题描述

在一次上线后,日志中出现空指针的报错,但是报错代码位置以及相应工具类未进行过修改,接下来进一步分析。

以下为报错堆栈信息:

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是进行反编译的,堆栈里提供的代码行数已经失真了,直接贴上报空指针的源码截图。

 


 

 


 

getMethodInfo入参member是null,从而导致空指针。需要通过断点跟踪运行时的变量值,找到setters数组中的元素是如何生成的。

 


 

(4)target是作为对象拷贝的目标对象的类,setters这个数组就是通过反射获取该目标类的所有具备读方法的描述对象(PropertyDescriptor对象,可以理解为属性/方法描述)。这里面方法名有些歧义,不是说只返回getter相关的属性对象,返回的是该类所有具备读或写方法的属性描述,两个布尔值的类型分别控制校验读或写。

 


 

 


 

综上,由于无法获取目标类的writeMethod,从而没有办法找到这个属性的写入方法,就没有办法对目标对象继续赋值。

 


 

此时方向就转到了目标类的实现上,分析到这里就跟Lombok产生了联系。此处确实被修改过,WaybillProcess类增加了@Accessors这个注解。

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

(5)WaybillProcess使用了@Accessors(chain = true)这个注解,这就回到了开头提到的,在使用了这个注解后该类生成的set方法返回值就不是void而是this,在通过Introspector获取属性描述时就不会被认定是写入方法,在去掉这个注解后,writeMethodName就有值了。

 


 

三、解决办法

解决办法1:删除该注解,将工程里链式set改成了常规的set赋值方式。

解决办法2:保留该注解,替换对象拷贝的工具类,建议使用MapStruct配合Lombok,直接在编译时生成get/set方法,更加安全,功能也更加强大。

四、总结

凡是依赖JDK Introspector获取类set方法描述的工具类、组件都会受到其写入方法定义导致的一些列问题,目前在工程实践中遇到了BeanCopier进行对象拷贝、BeanUtils对属性进行赋值都会遇到问题。所以大家在日常开发过程中,如果该类已经被大面积的使用,在使用组件特性时需要多留意。

对于对象拷贝已经有很多最佳实践了,有相关的文章大家可以推荐一下。

感谢阅读!

标签:set,java,BeanCopier,jar,cglib,注解,Lombok,null,指针
From: https://www.cnblogs.com/Jcloud/p/18261018

相关文章

  • Async 注解底层异步线程
    一、前言开发中我们经常会用到异步方法调用,具体到代码层面,异步方法调用的实现方式有很多种,比如最原始的通过实现Runnable接口或者继承Thread类创建异步线程,然后启动异步线程;再如,可以直接用java.util.concurrent包提供的线程池相关API实现异步方法调用。如果说可以用一行......
  • Spring常用注解,自动扫描装配Bean
    1引入context命名空间(在Spring的配置文件中),配置文件如下:Xml代码xmlns:context="http://www.springframework.org/schema/context"http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context-2.5.xsd......
  • @ComponentScan注解的属性详细使用
    指定包扫描,主要扫描该包下@Controller @Service @Respsitory @Component四个注解@ComponentScan(value="com.songzixian")指定排除要扫描的包@ComponentScan(value="com.songzixian",excludeFilters={@ComponentScan.Filter(type=FilterType.ANNOTATION,classes={Co......
  • 【C语言初学指导】进阶知识 指针、数组与字符串
    指针指针是一种特殊的变量,它存储的是另一个变量的内存地址,而不是数据值本身。这个地址指向了内存中的一个特定位置,程序可以通过这个地址来访问或修改存储在该位置的数据。举个例子:intvar=10;//假设var住在内存里的101号房间int*ptr=&var;//ptr就是我们......
  • recastnavigation.Sample_TempObstacles代码注解 - rcBuildHeightfieldLayers
    烘培代码在rcBuildHeightfieldLayers本质上是为每个tile生成高度上的不同layer算法的关键是三层循环:forz轴循环forx轴循环for高度span循环判断span和相邻span的连通性(x/z平面相邻cell)如果联通,则标注为同一个layer,也就是在x/z平面上标注layer,形成像是互不相......
  • C++(野指针)
    野指针(DanglingPointer)是在C++中指向无效内存位置的指针。野指针通常发生在以下几种情况下:释放了内存但没有将指针设为NULL:当使用delete或delete[]释放内存后,指针仍然保持指向原来的地址,但该地址已经无效。int*ptr=newint(10);deleteptr;//ptr现在是野指针栈上......
  • @Slf4j注解的使用
    作用为了少写两行代码,不用每次都在类的最前边写上那个:privatestaticfinalLoggerlogger=LoggerFactory.getLogger(this.XXX.class);只需要在类前面添加注解@Slf4j,即可使用log日志的功能引入确保idea中有Lombok插件(IntelliJIDEA2020.3及以上版本已经内置Lombokplugin......
  • 函数内部返回指向字符串的指针和数组名的区别
    目录两道题目进程的内存分布结论两道题目先来看两道与内存管理有关的题目以下程序会出错吗?如果不会则输出什么?#include<stdio.h>char*func(){ char*str="HelloWorld"; returnstr;}intmain(){ char*str=func(); //程序输出HelloWorld printf("%s\n",......
  • Spring5中常用的注解说明
    用于创建对象的注解相当于:<beanid=""class="">1.1@Component注解作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。属性:value:指定bean的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。1.2@Controller @Servic......
  • 深入理解指针(1)
    目录:1.内存和地址2.指针变量和地址3.指针变量类型的意义4.const修饰指针5.指针运算6.野指针7.assert断⾔8.指针的使⽤和传址调用1.内存和地址1.1内存在讲内存和地址之前,我们想有个⽣活中的案例:假设有⼀栋宿舍楼,把你放在楼⾥,楼上有100个房间,但是房间没......