@NonNull導致無法序列化的問題及解決
@NonNull導致無法序列化的問題
以上這個代碼在接參的時候報了一個缺少無參構(gòu)造函數(shù)無法序列化的錯誤
將.class反編譯
可以看到編譯后的源碼中生成了一個有參構(gòu)造 明顯是 用來判空的 假設那么這個構(gòu)造函數(shù)應該就是根據(jù)@NonNull生成的
實際上我們治理應該添加的注解是NotNull才對
上面因為lombook根據(jù)@NonNull生成了一個有參構(gòu)造函數(shù),導致jdk不會添加默認的無參構(gòu)造函數(shù),沒有無參構(gòu)造函數(shù)的話 序列化就會失敗.
@NonNull修飾Field反序列化部分值為空
一般Http接口,為了參數(shù)統(tǒng)一管理,定義一個VO用來接收POST過來的字段,常規(guī)做法是把參數(shù)解析成map,然后反序列化到VO中,早期定義的接口字段都非空,所以VO中都加了@NonNull注解;一直很和諧;
因為需求變化,接口字段需要增加兩個,為了版本兼容,新加的兩個字段需要可空;于是在VO中增加兩個字段,不用@NonNull修飾,但是反序列化后發(fā)現(xiàn)這兩個字段一直為空!怎么都不能從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
發(fā)現(xiàn)其是作用于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", ?? ??? ?}));
再看下經(jīng)過注解后編譯的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的構(gòu)造方法:只有一個帶@NonNull注解參數(shù)的構(gòu)造方法?。。?/p>
一般到這里都能想到反序列化后的為啥另外兩個未注解NonNull的為啥空值了;如果沒想到,也沒關(guān)系,咱們再來看看JSON反序列化的過程
2: json反序列化;
一系列遞進過程不再描述,重點看JavaBeanInfo類中的build方法,這個方法是真正把map反序化到javaBean的過程
public static JavaBeanInfo build(Class<?> clazz, Type type, PropertyNamingStrategy propertyNamingStrategy, boolean fieldBased, boolean compatibleWithJavaBean, boolean jacksonCompatible)?
挑幾處重要的開看下:
取構(gòu)造方法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()); ? ? ? ? ? ? } ? ? ? ? }
賦值創(chuàng)建javaBean的構(gòu)造
? ?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; ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? } ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? }
創(chuàng)建javaBean,看傳參; 只有一個構(gòu)造方法;
?if (!kotlin && !clazz.getName().equals("javax.servlet.http.Cookie")) { ? ? ? ? ? ? ? ? ? ? ? ? return new JavaBeanInfo(clazz, builderClass, (Constructor)null, creatorConstructor, (Method)null, (Method)null, jsonType, fieldList); ? ? ? ? ? ? ? ? ? ? }
結(jié)論:
使用@NonNull注解,編譯后生成的CLASS構(gòu)造方法只有一個,且只有被注解的字段才能構(gòu)造時候賦值;此種做法是保證在編譯期可以判斷非空;
反序列化時候使用了這個構(gòu)造方法,其他的值沒有被賦值;
建議改進
1: 使用@NotNull代替
2: 如果修飾的是String類型,推薦使用@NotBlank,好處是可以判斷空字符串
3: 在自定義的VO中增加一個無參構(gòu)造方法;
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
淺談Java循環(huán)中的For和For-each哪個更快
本文主要介紹了淺談Java循環(huán)中的For和For-each哪個更快,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2022-08-08Elasticsearch?自動重啟腳本創(chuàng)建實現(xiàn)
這篇文章主要為大家介紹了Elasticsearch?自動重啟腳本創(chuàng)建實現(xiàn)詳解分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-08-08設置Myeclipse中的代碼格式化、注釋模板及保存時自動格式化
這篇文章主要介紹了設置Myeclipse中的代碼格式化、注釋模板及保存時自動格式化方法,需要的朋友可以參考下2014-10-10logback的ShutdownHook關(guān)閉原理解析
這篇文章主要為大家介紹了logback的ShutdownHook關(guān)閉原理源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-11-11