@NonNull导致无法序列化的问题及解决
作者:if_icanfly 发布时间:2021-07-21 23:07:33
@NonNull导致无法序列化的问题
以上这个代码在接参的时候报了一个缺少无参构造函数无法序列化的错误
将.class反编译
可以看到编译后的源码中生成了一个有参构造 明显是 用来判空的 假设那么这个构造函数应该就是根据@NonNull生成的
实际上我们治理应该添加的注解是NotNull才对
上面因为lombook根据@NonNull生成了一个有参构造函数,导致jdk不会添加默认的无参构造函数,没有无参构造函数的话 序列化就会失败.
@NonNull修饰Field反序列化部分值为空
一般Http接口,为了参数统一管理,定义一个VO用来接收POST过来的字段,常规做法是把参数解析成map,然后反序列化到VO中,早期定义的接口字段都非空,所以VO中都加了@NonNull注解;一直很和谐;
因为需求变化,接口字段需要增加两个,为了版本兼容,新加的两个字段需要可空;于是在VO中增加两个字段,不用@NonNull修饰,但是反序列化后发现这两个字段一直为空!怎么都不能从map中获取到这两个值!
分析
版本:
JDK:1.8
lombok:1.18.12
fastjson:1.2.60
原代码
package com.example.demo;
import lombok.Data;
import lombok.NonNull;
@Data
public class DemoRequestVO {
@NonNull
private String firstParam;
private String SecondParam;
private String thirdParam;
}
public static void testDemo(){
Map<String, String> params = new HashMap<>();
params.put("firstParam","lllllll");
params.put("secondParam","45454645");
params.put("thirdParam","xx公司");
DemoRequestVO request = JSON.parseObject(JSON.toJSONString(params), DemoRequestVO.class);
System.out.println(request);
}
分析原因
做两方面猜测:
1: 注解提供者问题
2: Json反序列化问题
1: 先看下: 注解提供者 @NonNull
发现其是作用于RetentionPolicy.CLASS的
package lombok;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.LOCAL_VARIABLE, ElementType.TYPE_USE})
@Retention(RetentionPolicy.CLASS)
@Documented
public @interface NonNull {
}
查看lombok源码可以看到,NonNull注解提供者一共这么多
static {
NONNULL_ANNOTATIONS = Collections.unmodifiableList(Arrays.asList(new String[] {
"androidx.annotation.NonNull",
"android.support.annotation.NonNull",
"com.sun.istack.internal.NotNull",
"edu.umd.cs.findbugs.annotations.NonNull",
"javax.annotation.Nonnull",
// "javax.validation.constraints.NotNull", // The field might contain a null value until it is persisted.
"lombok.NonNull",
"org.checkerframework.checker.nullness.qual.NonNull",
"org.eclipse.jdt.annotation.NonNull",
"org.eclipse.jgit.annotations.NonNull",
"org.jetbrains.annotations.NotNull",
"org.jmlspecs.annotation.NonNull",
"org.netbeans.api.annotations.common.NonNull",
"org.springframework.lang.NonNull",
}));
再看下经过注解后编译的CLASS
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package com.example.demo;
import lombok.NonNull;
public class DemoRequestVO {
@NonNull
private String firstParam;
private String SecondParam;
private String thirdParam;
public DemoRequestVO(@NonNull final String firstParam) {
if (firstParam == null) {
throw new NullPointerException("firstParam is marked non-null but is null");
} else {
this.firstParam = firstParam;
}
}
@NonNull
public String getFirstParam() {
return this.firstParam;
}
public String getSecondParam() {
return this.SecondParam;
}
public String getThirdParam() {
return this.thirdParam;
}
public void setFirstParam(@NonNull final String firstParam) {
if (firstParam == null) {
throw new NullPointerException("firstParam is marked non-null but is null");
} else {
this.firstParam = firstParam;
}
}
public void setSecondParam(final String SecondParam) {
this.SecondParam = SecondParam;
}
public void setThirdParam(final String thirdParam) {
this.thirdParam = thirdParam;
}
public boolean equals(final Object o) {
if (o == this) {
return true;
} else if (!(o instanceof DemoRequestVO)) {
return false;
} else {
DemoRequestVO other = (DemoRequestVO)o;
if (!other.canEqual(this)) {
return false;
} else {
label47: {
Object this$firstParam = this.getFirstParam();
Object other$firstParam = other.getFirstParam();
if (this$firstParam == null) {
if (other$firstParam == null) {
break label47;
}
} else if (this$firstParam.equals(other$firstParam)) {
break label47;
}
return false;
}
Object this$SecondParam = this.getSecondParam();
Object other$SecondParam = other.getSecondParam();
if (this$SecondParam == null) {
if (other$SecondParam != null) {
return false;
}
} else if (!this$SecondParam.equals(other$SecondParam)) {
return false;
}
Object this$thirdParam = this.getThirdParam();
Object other$thirdParam = other.getThirdParam();
if (this$thirdParam == null) {
if (other$thirdParam != null) {
return false;
}
} else if (!this$thirdParam.equals(other$thirdParam)) {
return false;
}
return true;
}
}
}
protected boolean canEqual(final Object other) {
return other instanceof DemoRequestVO;
}
public int hashCode() {
int PRIME = true;
int result = 1;
Object $firstParam = this.getFirstParam();
int result = result * 59 + ($firstParam == null ? 43 : $firstParam.hashCode());
Object $SecondParam = this.getSecondParam();
result = result * 59 + ($SecondParam == null ? 43 : $SecondParam.hashCode());
Object $thirdParam = this.getThirdParam();
result = result * 59 + ($thirdParam == null ? 43 : $thirdParam.hashCode());
return result;
}
public String toString() {
return "DemoRequestVO(firstParam=" + this.getFirstParam() + ", SecondParam=" + this.getSecondParam() + ", thirdParam=" + this.getThirdParam() + ")";
}
}
重点是看这个编译后的class的构造方法:只有一个带@NonNull注解参数的构造方法!!!
一般到这里都能想到反序列化后的为啥另外两个未注解NonNull的为啥空值了;如果没想到,也没关系,咱们再来看看JSON反序列化的过程
2: json反序列化;
一系列递进过程不再描述,重点看JavaBeanInfo类中的build方法,这个方法是真正把map反序化到javaBean的过程
public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy, boolean fieldBased, boolean compatibleWithJavaBean, boolean jacksonCompatible)
挑几处重要的开看下:
取构造方法list
Constructor[] constructors = clazz.getDeclaredConstructors();
Constructor<?> defaultConstructor = null;
if (!kotlin || constructors.length == 1) {
if (builderClass == null) {
defaultConstructor = getDefaultConstructor(clazz, constructors);
} else {
defaultConstructor = getDefaultConstructor(builderClass, builderClass.getDeclaredConstructors());
}
}
赋值创建javaBean的构造
boolean is_public = (constructor.getModifiers() & 1) != 0;
if (is_public) {
String[] lookupParameterNames = ASMUtils.lookupParameterNames(constructor);
if (lookupParameterNames != null && lookupParameterNames.length != 0 && (creatorConstructor == null || paramNames == null || lookupParameterNames.length > paramNames.length)) {
paramNames = lookupParameterNames;
creatorConstructor = constructor;
}
}
创建javaBean,看传参; 只有一个构造方法;
if (!kotlin && !clazz.getName().equals("javax.servlet.http.Cookie")) {
return new JavaBeanInfo(clazz, builderClass, (Constructor)null, creatorConstructor, (Method)null, (Method)null, jsonType, fieldList);
}
结论:
使用@NonNull注解,编译后生成的CLASS构造方法只有一个,且只有被注解的字段才能构造时候赋值;此种做法是保证在编译期可以判断非空;
反序列化时候使用了这个构造方法,其他的值没有被赋值;
建议改进
1: 使用@NotNull代替
2: 如果修饰的是String类型,推荐使用@NotBlank,好处是可以判断空字符串
3: 在自定义的VO中增加一个无参构造方法;
来源:https://blog.csdn.net/if_icanfly/article/details/121537340
猜你喜欢
- 在使用AbstractRoutingDataSource配置多数据源时,发现使用@aspect配置的DataSourceSwitchAspe
- 本文为大家介绍了java图片添加水印实例代码,java实现水印还是非常方便的,水印可以是图片或者文字,具体内容如下package micha
- 正文将 Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要
- — 遇到问题今天在IDEA里面运行项目的时候报了一个错,如下图所示:— 找到问题根源其实控制台给出的错误信息提示说的很明显:类加载器加载文件
- 如下所示:String beginDate="1328007600000";SimpleDateFormat sdf=n
- 这篇文章主要介绍了java通过实例了解值传递和引用传递,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋
- 一、概述Socket类是Java执行客户端TCP操作的基础类,这个类本身使用代码通过主机操作系统的本地TCP栈进行通信。Socket类的方法
- 前言一般情况下,多数移动开发者使用的是数据线连接电脑,进行各种移动设备的调试,更有胜者,非常迷恋模拟器,模拟器它好不好,答案是好,因为直接运
- 静态代理: 由我们开发者自己手动创建或者在程序运行前就已经存在的代理类,静态代理通常只代理一个类, * 是代理一个接口下的多个实现类。动态
- 需要用到 java 写一个 ftp 的工具,因为只有一点点 java 基础,但是由于好几年不用,几乎算是不会了,只好一点点来搞,还好能捡起来
- 1. 人机对战要增添一个人机对战的模块, 最大的难点就是如何让人机知道下在什么位置是最好的, 不仅要具备进攻的能力, 还需要具备防守的能力.
- 介绍众所周知,AOP(面向切面编程)是Spring框架的特色功能之一。通过设置横切关注点(cross cutting concerns),A
- 了解JVM内存结构的目的在Java的开发过程中,因为有JVM自动内存管理机制,不再需要像在C、C++开发那样手动释放对象的内存空间,不容易出
- 本文实例讲述了.NET WinForm实现在listview中添加progressbar的方法。分享给大家供大家参考,具体如下:找了好长时间
- 问题(1)重入锁是什么?(2)ReentrantLock如何实现重入锁?(3)ReentrantLock为什么默认是非公平模式?(4)Ree
- 开始 在本文中,我将展示如何使用各种不同的 Java 技术构建一些简单的 Comet 风格的 Web 应
- 大家可以自行百度下阿里分布式事务,在这里我就不啰嗦了。下面是阿里分布式事务开源框架的一些资料,本文是springboot+dubbo+fes
- 封装类用于阻止系统休眠的C#类。以下是代码注释的解释:DllImport("kernel32.dll"):定义了一个AP
- 工具方法:本文的目的是把json串转成map键值对存储,而且只存储叶节点的数据maven 引用jar包版本:<dependency&g
- 多数据源的目的在于一个代码模块可调用多个数据库的数据进行某些业务操作。MyBatis-Plus开发者写了一个多数据源叫dynamic-dat