前段時間新增一個特別簡單的功能,晚上上線前review程式碼時想到公司拼搏進取的價值觀臨時加一行log日誌,覺得就一行簡單的日誌基本上沒啥問題,結果剛上完線後一堆報警,趕緊回滾了程式碼,找到問題刪除了添加日誌的程式碼,重新上線完畢。
情景還原
定義了一個 CountryDTO
public classCountryDTO{
private String country;
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
returnthis.country;
}
publicBoolean isChinaName() {
returnthis.country.equals("中國");
}
}
定義測試類 FastJonTest
public classFastJonTest {
@Test
publicvoidtestSerialize() {
CountryDTO countryDTO = new CountryDTO();
String str = JSON.toJSONString(countryDTO);
System.out.println(str);
}
}
執行時報空指標錯誤:
透過報錯資訊可以看出來是 序列化的過程中執行了 isChinaName()方法,這時候this.country變量為空, 那麽問題來了:
序列化為什麽會執行isChinaName()呢?
引申一下,序列化過程中會執行那些方法呢?
源分碼析
透過debug觀察呼叫鏈路的堆疊資訊
呼叫鏈中的ASMSerializer_1_CountryDTO.write是FastJson使用asm技術動態生成了一個類ASMSerializer_1_CountryDTO,
asm技術其中一項使用場景就是透過到動態生成類用來代替java反射,從而避免重復執行時的反射開銷
JavaBeanSerizlier序列化原理
透過下圖看出序列化的過程中,主要是呼叫JavaBeanSerializer類的write()方法。
而JavaBeanSerializer 主要是透過 getObjectWriter()方法獲取,透過對getObjectWriter()執行過程的偵錯,找到比較關鍵的com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializer方法,進而找到 com.alibaba.fastjson.util.TypeUtils#computeGetters
public static List<FieldInfo> computeGetters( class<?> clazz, //
JSONType jsonType, //
Map<String,String> aliasMap, //
Map<String,Field> fieldCacheMap, //
boolean sorted, //
PropertyNamingStrategy propertyNamingStrategy //
){
//省略部份程式碼....
Method[] methods = clazz.getMethods();
for(Method method : methods){
//省略部份程式碼...
if(method.getReturnType().equals(Void.TYPE)){
continue;
}
if(method.getParameterTypes().length != 0){
continue;
}
//省略部份程式碼...
JSONField annotation = TypeUtils.getAnnotation(method, JSONField. class);
//省略部份程式碼...
if(annotation != null){
if(!annotation.serialize()){
continue;
}
if(annotation.name().length() != 0){
//省略部份程式碼...
}
}
if(methodName.startsWith("get")){
//省略部份程式碼...
}
if(methodName.startsWith("is")){
//省略部份程式碼...
}
}
}
從程式碼中大致分為三種情況:
@JSONField(.serialize = false, name = "xxx")註解
getXxx() : get開頭的方法
isXxx():is開頭的方法
序列化流程圖
範例程式碼
/**
* case1: @JSONField(serialize = false)
* case2: getXxx()返回值為void
* case3: isXxx()返回值不等於布爾型別
* case4: @JSONType(ignores = "xxx")
*/
@JSONType(ignores = "otherName")
public classCountryDTO {
private String country;
publicvoidsetCountry(String country) {
this.country = country;
}
public String getCountry() {
returnthis.country;
}
publicstaticvoidqueryCountryList() {
System.out.println("queryCountryList()執行!!");
}
public Boolean isChinaName() {
System.out.println("isChinaName()執行!!");
returntrue;
}
public String getEnglishName() {
System.out.println("getEnglishName()執行!!");
return"lucy";
}
public String getOtherName() {
System.out.println("getOtherName()執行!!");
return"lucy";
}
/**
* case1: @JSONField(serialize = false)
*/
@JSONField(serialize = false)
public String getEnglishName2() {
System.out.println("getEnglishName2()執行!!");
return"lucy";
}
/**
* case2: getXxx()返回值為void
*/
publicvoidgetEnglishName3() {
System.out.println("getEnglishName3()執行!!");
}
/**
* case3: isXxx()返回值不等於布爾型別
*/
public String isChinaName2() {
System.out.println("isChinaName2()執行!!");
return"isChinaName2";
}
}
執行結果為:
isChinaName()執行!!
getEnglishName()執行!!
{"chinaName":true,"englishName":"lucy"}
程式碼規範
可以看出來序列化的規則還是很多的,比如有時需要關註返回值,有時需要關註參數個數,有時需要關註@JSONType註解,有時需要關註@JSONField註解;當一個事物的判別方式有多種的時候,由於團隊人員掌握知識點的程度不一樣,這個變異數很容易導致程式碼問題,所以盡量有一種推薦方案。這裏推薦使用@JSONField(serialize = false)來顯式的標註方法不參與序列化,下面是使用推薦方案後的程式碼,是不是一眼就能看出來哪些方法不需要參與序列化了。
public classCountryDTO{
private String country;
public void setCountry(String country) {
this.country = country;
}
public String getCountry() {
returnthis.country;
}
@JSONField(serialize = false)
public static void queryCountryList() {
System.out.println("queryCountryList()執行!!");
}
publicBoolean isChinaName() {
System.out.println("isChinaName()執行!!");
returntrue;
}
public String getEnglishName() {
System.out.println("getEnglishName()執行!!");
return"lucy";
}
@JSONField(serialize = false)
public String getOtherName() {
System.out.println("getOtherName()執行!!");
return"lucy";
}
@JSONField(serialize = false)
public String getEnglishName2() {
System.out.println("getEnglishName2()執行!!");
return"lucy";
}
@JSONField(serialize = false)
public void getEnglishName3() {
System.out.println("getEnglishName3()執行!!");
}
@JSONField(serialize = false)
public String isChinaName2() {
System.out.println("isChinaName2()執行!!");
return"isChinaName2";
}
}
三個頻率高的序列化的情況
以上流程基本遵循 發現問題 --> 原理分析 --> 解決問題 --> 昇華(編程規範)。
圍繞業務上:解決問題 -> 如何選擇一種好的額解決方案 -> 好的解決方式如何擴充套件n個系統套用;
圍繞技術上:解決單個問題,順著單個問題掌握這條線上的原理。
來源:網路
>>
END
精品資料,超贊福利,免費領
微信掃碼/長按辨識 添加【技術交流群】
群內每天分享精品學習資料
最近開發整理了一個用於速刷面試題的小程式;其中收錄了上千道常見面試題及答案(包含基礎、並行、JVM、MySQL、Redis、Spring、SpringMVC、SpringBoot、SpringCloud、訊息佇列等多個型別),歡迎您的使用。
👇👇
👇點選"閱讀原文",獲取更多資料(持續更新中)