背景
前幾周在做專案fetch
切換,即將HttpUtils
呼叫改成使用Feign
呼叫。大概程式碼如下:
// 原始碼
String resultJson = HttpUtil.get(url + "/fin/test?code=" + code, null);
RespDTO<Result> respDTO = JSON.parseObject(resultJson, new TypeReference<RespDTO<Result>>() {});
// 現程式碼如下
RespDTO<Result> respDTO = urlClient.getTest(code);
程式碼上線後,出現了異常。表現為:respDTO
的某個欄位為null
,但是第三方是有傳過來這個值的。
問題復現
public static void main(String[] args) throws JsonProcessingException, IntrospectionException {
String data = "{\"aFiled\":10,\"normalFiled\":20}";
// 原方式
Domain domain = JSON.parseObject(data, Domain.class);
System.out.println("domain = " + domain.getAFiled());
// feign方式
ObjectMapper mapper = new ObjectMapper();
Domain domain1 = mapper.readValue(data, Domain.class);
System.out.println("domain1 = " + domain1.getAFiled());
}
問題分析
既然請求返回資料,但接收物件沒有對應的值,那就說明字串沒有反序列化到指定的變數名上。改之前是使用FastJson
反序列化資料,改之後的Feign
預設是採用Jackson
反序列化資料。那麼為什麼FastJson
可以反序列化aFiled
而Jackson
不可以呢?
FastJson
是根據變數名稱來反序列化的,也就是說它接收到aFiled
資料,就會到物件找aFiled
變數名,然後附上值;而Jackson
預設是根據JavaBean
規範找到對應屬性後再賦值,也就是說Jackson
並沒有在這個物件找到aFiled
屬性。
JavaBean
屬性名稱跟變數名稱是不一定相同的。JavaBean
是通過物件的get
、set
方法來確定物件屬性,其屬性名稱是由其物件變數來決定的,通常的邏輯是:將屬性首字母轉成大寫。但也有例外就是,前倆個字母都是大寫的情況:
8.8 Capitalization of inferred names.
When we use design patterns to infer a property or event name, we need to decide what rulesto follow for capitalizing the inferred name. If we extract the name from the middle of a normalmixedCase style Java name then the name will, by default, begin with a capital letter.
Java programmers are accustomed to having normal identifiers start with lower case letters.Vigorous reviewer input has convinced us that we should follow this same conventional rulefor property and event names.
Thus when we extract a property or event name from the middle of an existing Java name, wenormally convert the first character to lower case. However to support the occasional use of allupper-case names, we check if the first two characters of the name are both upper case and ifso leave it alone. So for example,
“FooBah” becomes “fooBah”
“Z” becomes “z”
“URL” becomes “URL”
We provide a method Introspector.decapitalize which implements this conversion rule.
而Jackson
根據get
、set
方法來確定屬性的名稱。而變數前倆個字母含有一個大寫字母對應的屬性名稱會很怪。如下:
我們專案上使用的是lombok
,其生成的get
、set
方法是不遵循JavaBean
規範的,只是將變數名的首字母大寫而已,所以它生成aFiled
的方法是getAFiled
。
所以,Jackson
在接收到aFiled
屬性值,它會到物件找setaFiled
方法,自然這個物件是沒有這個方法的,所以就沒對映到aFiled
欄位上。
解決辦法
jackson
可以使用@JsonProperty
註解來指定屬性名稱。
總結
出現這個就是因為JavaBean
規範對前倆個字母含有大寫字母的變數名做了特殊處理。 Jackson
遵循JavaBean
規範來反序列化,而專案使用的Lombok
外掛是不遵循JavaBean
規範。
擴充套件
JavaBean
規範對前倆個字母含有大寫字母的處理不全
針對變數名前倆個字母做個窮舉,就是如下物件:
public class Domain {
private Integer aFiled;
private Integer afiled;
private Integer AFiled;
private Integer Afiled;
public Integer getaFiled() {
return aFiled;
}
public void setaFiled(Integer aFiled) {
this.aFiled = aFiled;
}
public Integer getAfiled() {
return afiled;
}
public void setAfiled(Integer afiled) {
this.afiled = afiled;
}
public Integer getAFiled() {
return AFiled;
}
public void setAFiled(Integer AFiled) {
this.AFiled = AFiled;
}
}
你會發現afiled
和Afiled
生成的屬性是一致的!不過好在專案中沒有人只將首字母大寫這種命名風格。
謹慎用Lombok
替換舊專案的get
、set
方法
舊專案一般都是用Idea
來生成get
、set
方法,而Idea
是遵循JavaBean
規範來生成屬性的。所以,舊專案如果含有前倆個僅含有一個大寫字母時,儘量不用藥lombok
去替換。