Java變數命名前倆個字母僅含有一個大寫字母的坑
背景
前幾周在做項目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
去替換。