一個(gè)Getter引發(fā)的血案
1需求
最近做一了個(gè)需求,調(diào)用其他服務(wù)的REST接口,感覺很簡單,于是迅速就搞起來了
構(gòu)造Request類
public?class?User?{
????private?String?name;
????private?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
}
啪,我上來就一new
service.sendRequest(new?User("niu",?18));
打完,收工,又是努力工作(摸魚)的一天。
2定位
但是,某天晚上8點(diǎn),測試人員突然給我打電話,說調(diào)用失敗,同時(shí)本身又缺少打印,沒有辦法具體哪出問題了。
我是不會認(rèn)為這么簡單的代碼自己會出錯(cuò)的,不可能!!
經(jīng)過網(wǎng)絡(luò)抓包后發(fā)現(xiàn),收到的參數(shù)都是null,但是我這邊明明調(diào)用構(gòu)造器傳入?yún)?shù)了

難道出現(xiàn)靈異事件了?
經(jīng)過分析,整體數(shù)據(jù)流為:

能出現(xiàn)問題的地方只能是序列化JSON地方,于是本地測試驗(yàn)證了這一結(jié)論:
public?static?void?main(String[]?args)?throws?IOException?{
????ObjectMapper?objectMapper?=?new?ObjectMapper();
????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????System.out.println(request);
}
雖然是出問題了,但是序列化并沒有轉(zhuǎn)為屬性為null的對象,而是直接拋出異常
Exception?in?thread?"main"?com.fasterxml.jackson.databind.exc.InvalidDefinitionException:?No?serializer?found?for?class?online.jvm.bean.User?and?no?properties?discovered?to?create?BeanSerializer?(to?avoid?exception,?disable?SerializationFeature.FAIL_ON_EMPTY_BEANS)
?at?com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77)
通過查詢異常資料,解決掉這種異常需要在增加Jackson的序列化配置FAIL_ON_EMPTY_BEANS,F(xiàn)AIL_ON_EMPTY_BEANS這個(gè)配置表示如果某個(gè)bean序列化為空時(shí)不會異常失敗
public?static?void?main(String[]?args)?throws?IOException?{
????ObjectMapper?objectMapper?=?new?ObjectMapper();
????objectMapper.configure(FAIL_ON_EMPTY_BEANS,?false);
????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????System.out.println(request);
}
這種就不會報(bào)錯(cuò),而是返回序列化成空串,也就導(dǎo)致接受方為屬性都為null
通過看自研RPC框架看到是有該FAIL_ON_EMPTY_BEANS的配置
3解決
再來分析一下原因,Jackson序列化時(shí)需要調(diào)用bean的getter方法
1、寫上getter后再看下結(jié)果:
public?class?User?{
????private?String?name;
????private?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?String?getName()?{
????????return?name;
????}
????public?Integer?getAge()?{
????????return?age;
????}
????public?static?void?main(String[]?args)?throws?IOException?{
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????????System.out.println(request);
????????//?輸出正常?:?{"name":"niu","age":18}
????}
}
2、或者把屬性訪問權(quán)限改為public
public?class?User?{
????public?String?name;
????public?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?static?void?main(String[]?args)?throws?IOException?{
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????????System.out.println(request);
????????//?輸出正常?:?{"name":"niu","age":18}
????}
}
但是如果要求不能暴露bean的屬性即使是getter也不行呢?
3、注解 @JsonProperty
這是就需要使用Jackson提供的注解 @JsonProperty
public?class?User?{
????@JsonProperty("userName")
????private?String?name;
????@JsonProperty
????private?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?static?void?main(String[]?args)?throws?IOException?{
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????????System.out.println(request);
????????//???{"userName":"niu","age":18}
????}
}
來看下注解@JsonProperty的源碼注釋
Marker?annotation?that?can?be?used?to?define?a?non-static?method?as?a?"setter"?or?"getter"?for?a?logical?property?(depending?on?its?signature),?or?non-static?object?field?to?be?used?(serialized,?deserialized)?as?a?logical?property.
大體意思是注解如果用在屬性上相當(dāng)于為該屬性定義getter和setter。
那如果既有g(shù)etter又有@JsonProperty注解,以哪個(gè)為準(zhǔn)呢?
public?class?User?{
????@JsonProperty("userName")
????private?String?name;
????@JsonProperty
????private?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?String?getName()?{
????????return?name;
????}
????public?static?void?main(String[]?args)?throws?IOException?{
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????????System.out.println(request);
????????//?{"age":18,"userName":"niu"}
????}
}
如果getter一個(gè)沒有的屬性,效果如何呢?
public?class?User?{
????@JsonProperty("userName")
????private?String?name;
????@JsonProperty
????private?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.name?=?name;
????????this.age?=?age;
????}
????public?String?getName2()?{
????????return?name;
????}
????public?static?void?main(String[]?args)?throws?IOException?{
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????????System.out.println(request);
????????//?{"age":18,"name2":"niu","userName":"niu"}
????}
}
這說明如果有@JsonProperty注解,先以注解為準(zhǔn)
然后利用反射找到對象類的所有g(shù)et方法,接下來去get,然后小寫化,作為json的每個(gè)key值,而get方法的返回值作為value。接下來再反射field,添加到j(luò)son中。
4、特殊情況
還有一種比較特殊的情況, getter方法由lombok生成,且屬性的次首字母是大寫:
@Getter
public?class?User?{
????@JsonProperty
????private?String?nAme;
????@JsonProperty
????private?Integer?age;
????public?User(String?name,?Integer?age)?{
????????this.nAme?=?name;
????????this.age?=?age;
????}
????public?static?void?main(String[]?args)?throws?IOException?{
????????ObjectMapper?objectMapper?=?new?ObjectMapper();
????????String?request?=?objectMapper.writeValueAsString(new?User("niu",?18));
????????System.out.println(request);
????????//?{"nAme":"niu","age":18,"name":"niu"}
????}
}
這是因?yàn)閘ombok生成的getter會把屬性的第一個(gè)字母變成大寫,
序列化時(shí)會把get后與小寫字母中間的大寫變成小寫,也就是會把NA變成小寫
所以序列化結(jié)果會有name(getter獲取)和nAme(注解獲取)兩個(gè)屬性
public?String?getNAme()?{
????return?this.nAme;
}
如果我們自己用idea快捷鍵生成getter,
此時(shí)之后序列化nAme
public?String?getnAme()?{
????return?nAme;
}
4小結(jié)
許多bug都是在自以為沒有問題的地方產(chǎn)生,看似簡單,更需要小心,同時(shí)也需要多注意序列化原理,整體感覺序列化還是用Gson更省心,完全不用關(guān)心Getter和Setter方法,會完全按照屬性名來序列化。
本文的涉及的bug過程和解決方式希望對你也有所幫助,再見。
—?【 THE END 】— 本公眾號全部博文已整理成一個(gè)目錄,請?jiān)诠娞柪锘貜?fù)「m」獲取! 最近面試BAT,整理一份面試資料《Java面試BATJ通關(guān)手冊》,覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。
獲取方式:點(diǎn)“在看”,關(guān)注公眾號并回復(fù) PDF?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。
文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。
謝謝支持喲 (*^__^*)
