@NonNull导致无法序列化的问题及解决

目录
  • @NonNull导致无法序列化的问题
  • @NonNull修饰Field反序列化部分值为空
    • 分析
    • 建议改进
  • 总结

@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中增加一个无参构造方法;

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java分析讲解序列化与字典功能的序列化

    目录 两种解决方案 字典注解定义 字典序列化与返序列化器的实现 字典序列化与反序列工具类 字典转换服务类 字典缓存服务 两种解决方案 前端查询字典数据然后前端转码 后端查询字典值,然后再转码返回给前段. 本文及时针对方案2 进行的改进 目标: 在需要返回给前段的字段上添加指定的注解例如:@DictDesc 则根据该字段定义的值结合注解配置生成 xxxDesc字段并自动赋值为注解属性值所对应的字典描述: 具体使用的技术涉及到jackson序列化与反序列化,其他JSON工具包也类型的效果; 字典注解

  • 详细聊聊RabbitMQ竟无法反序列化List问题

    目录 前言 问题重现 项目依赖 发送方 接收方 错误日志 分析问题原因 解决办法 总结 前言 最近在接到了一个需求,大概是通过RabbitMq给xx子系统同步用户数据,要提供单个同步和批量同步.内心暗喜这不简单的很嘛.三下五除二就把代码给写完了,大概长这样: public void syncUserSingle(User user) { // 省略一大堆业务代码 rabbitTemplate.convertAndSend("q_sync_user_single", user); } p

  • Java JDBC导致的反序列化攻击原理解析

    这篇文章主要介绍了Java JDBC导致的反序列化攻击原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 背景 上周BlackHat Europe 2019的议题<New Exploit Technique In Java Deserialization Attack>中提到了一个通过注入JDBC URL实现反序列化攻击的场景,简单分析一下. 分析 首先,当java应用使用MySQL Connector/J(官方的JDBC驱动,本文基于其

  • @NonNull导致无法序列化的问题及解决

    目录 @NonNull导致无法序列化的问题 @NonNull修饰Field反序列化部分值为空 分析 建议改进 总结 @NonNull导致无法序列化的问题 以上这个代码在接参的时候报了一个缺少无参构造函数无法序列化的错误 将.class反编译 可以看到编译后的源码中生成了一个有参构造 明显是 用来判空的 假设那么这个构造函数应该就是根据@NonNull生成的 实际上我们治理应该添加的注解是NotNull才对 上面因为lombook根据@NonNull生成了一个有参构造函数,导致jdk不会添加默认的

  • Mysql/MariaDB启动时处于进度条状态导致启动失败的原因及解决办法

    今天打开网站突然发现网站无法打开,后来通过SSH登陆服务器发现MARIADB数据库没有启动成功,再次启动还是无法成功启动,一直处于启动进度条,进度条结束后提示ERROR.查看日志出现以下错误: InnoDB: Unable to lock ./ibdata1, error: 11 后经调试发现是因为MariaDB数据库所在分区已经满了,造成无法启动. 只有将MariaDB数据库存放数据目录移动到另外一个磁盘份额比较大的分区或者将当前分配删除一些不必要的文件. 移动办法: 1.停掉mysql服务器

  • BootStrap Validator 版本差异问题导致的submitHandler失效问题的解决方法

    我用过的两个版本: v0.5.2-dev,0.4.5 这里针对于提交方法进行说明一下,如下代码: <script> $(function () { $("#addUserForm").bootstrapValidator({ submitHandler: function(validator, form, submitButton) { // 版本号0.4.5支持 // 版本号v0.5.2-dev不再支持submitHandler配置 } }).on("succe

  • jQuery插件Easyui设置datagrid的pageNumber导致两次请求问题的解决方法

    本文实例讲述了jQuery插件Easyui设置datagrid的pageNumber导致两次请求问题的解决方法.分享给大家供大家参考,具体如下: 一.问题描述: $('#tb-page-list').datagrid({ url: '/BisOrderInfo/GetList', pageNumber: 2, pageSize: 10 }); 当手动设置 pageNumber大于或等于2时,查看请求的方法时,会请求2次,并且第二次的pageNumber等于1: 二.原因所在: jquery.ea

  • 中文路径导致unitpngfix.js不正常的解决方法

    双击html页面在ie6里打开正常.但是发布到网站后,网站路径里有中文路径导致png图片不显示.解决方法:修改网站路径中的中文,改为英文.

  • listView的item中有checkbox,导致setOnItemClick失效的原因及解决办法

     一:item的根布局设置 Android:clickable="true",之后导致item点击事件失效,对根布局设置android:descendantFocusability="blocksDescendants",以及对checkbox设置android:focusable="false"都不会起作用,所以item根布局不要设置android:clickable="true" 二:item根布局设置android:de

  • webpack配置导致字体图标无法显示的解决方法

    问题:在项目开发时使用字体图标,报错如下: 所有的字体图标都不能正常显示了,报错提示不能解码字体. 解决问题:找了很久,最后发现是在webpack配置的时候自己手动添加了下面的代码而引起的错误:在 webpack.base.conf.js文件中删除 { test: /\.(eot|svg|ttf|woff|woff2)(\?\S*)?$/, loader: 'file-loader' }, 总结: 其实采用vue-cli 自动生成的配置文件已经完成了字体图标文件的编译,自己写的和自动生成的产生冲

  • VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法分析

    本文实例讲述了VUE 直接通过JS 修改html对象的值导致没有更新到数据中解决方法.分享给大家供大家参考,具体如下: 业务场景 我们在使用vue 编写 代码时,我们有一个 多行文本框控件,希望在页面点击一个按钮 在 文本框焦点位置插入一个 {pk}的数据. 发现插入 这个数据后,这个数据并没有同步到 数据中,但是直接通过键盘输入,就可以改变数据. 原因分析 在通过 JS 修改控件的value 数据后,并没有触发到数据更新. 解决办法 Vue.component('rx-textarea', {

  • Jackson序列化丢失泛型的解决

    Jackson序列化丢失泛型 经过 项目中遇到一个奇怪的bug,即一个Map<Integer,List<Integer>>的泛型map,向map中get一个存在的key,事实上却返回null. 经过排查,发现是该map被Jackson序列化后,key的类型从Integer变成了String类型.再经过反序列化,即使已经声明key泛型的Integer,反序列化后内存数据中的key为String并不是Integer类型且并未抛出异常. 复现 1.声明一个key泛型为Integer的ma

  • 因Spring AOP导致@Autowired依赖注入失败的解决方法

    发现问题: 之前用springAOP做了个操作日志记录,这次在往其他类上使用的时候,service一直注入失败,找了网上好多内容,发现大家都有类似的情况出现,但是又和自己的情况不太符合.后来总结自己的情况发现:方法为private修饰的,在AOP适配的时候会导致service注入失败,并且同一个service在其他的public方法中就没有这种情况,十分诡异. 解决过程: 结合查阅的资料进行了分析:在org.springframework.aop.support.AopUtils中: publi

随机推荐