Java變數命名前倆個字母僅含有一個大寫字母的坑

fourther發表於2020-10-11

背景

前幾周在做專案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());
}

https://github.com/wangjie-fourth/jackson01

問題分析

既然請求返回資料,但接收物件沒有對應的值,那就說明字串沒有反序列化到指定的變數名上。改之前是使用FastJson反序列化資料,改之後的Feign預設是採用Jackson反序列化資料。那麼為什麼FastJson可以反序列化aFiledJackson不可以呢?

FastJson是根據變數名稱來反序列化的,也就是說它接收到aFiled資料,就會到物件找aFiled變數名,然後附上值;而Jackson預設是根據JavaBean規範找到對應屬性後再賦值,也就是說Jackson並沒有在這個物件找到aFiled屬性。

JavaBean屬性名稱跟變數名稱是不一定相同的。JavaBean是通過物件的getset方法來確定物件屬性,其屬性名稱是由其物件變數來決定的,通常的邏輯是:將屬性首字母轉成大寫。但也有例外就是,前倆個字母都是大寫的情況:

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根據getset方法來確定屬性的名稱。而變數前倆個字母含有一個大寫字母對應的屬性名稱會很怪。如下:

我們專案上使用的是lombok,其生成的getset方法是不遵循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;
    }
}

你會發現afiledAfiled生成的屬性是一致的!不過好在專案中沒有人只將首字母大寫這種命名風格。

謹慎用Lombok替換舊專案的getset方法

舊專案一般都是用Idea來生成getset方法,而Idea是遵循JavaBean規範來生成屬性的。所以,舊專案如果含有前倆個僅含有一個大寫字母時,儘量不用藥lombok去替換。

參考連結

java bean規範文件
jackson 序列化問題

相關文章