一個(gè)Getter引發(fā)的血案
本文轉(zhuǎn)載自微信公眾號(hào)「你呀不牛」,作者不牛。轉(zhuǎn)載本文請聯(lián)系你呀不牛公眾號(hào)。
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í)本身又缺少打印,沒有辦法具體哪出問題了。
我是不會(huì)認(rèn)為這么簡單的代碼自己會(huì)出錯(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í)不會(huì)異常失敗
- 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);
- }
這種就不會(huì)報(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會(huì)把屬性的第一個(gè)字母變成大寫,
序列化時(shí)會(huì)把get后與小寫字母中間的大寫變成小寫,也就是會(huì)把NA變成小寫
所以序列化結(jié)果會(huì)有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方法,會(huì)完全按照屬性名來序列化。
本文的涉及的bug過程和解決方式希望對你也有所幫助,再見。