A line of log logs, resulting in an online accident of P1

picture

Source: juejin.cn/post/7156439842958606349

  • Online Incident Review

  • Scenario restoration

  • Source code analysis

    • JavaBeanSerizlier serialization principle

    • Serialization flowchart

  • sample code

  • code specification

  • Three cases of high frequency serialization


Online Incident Review

Some time ago, a colleague added a very simple function. When the code was launched at night, reviewhe thought of the company’s hard-working and enterprising values. He temporarily added a line of log logs. He thought that there was basically no problem with just one simple log line. Called the police, quickly rolled back the code, found the problem, deleted the code for adding logs, and went online again.

A front-end and back-end separated blog based on Spring Boot + MyBatis Plus + Vue 3.2 + Vite + Element Plus, including a background management system that supports functions such as articles, categories, tag management, and dashboards.

  • GitHub address: https://github.com/weiwosuoai/WeBlog

  • Gitee Address: https://gitee.com/AllenJiang/WeBlog

 
 

Scenario restoration

defines a CountryDTO

public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    public Boolean isChinaName() {
        return this.country.equals("中国");
    }
}

Define the test class FastJonTest

public class FastJonTest {
    @Test
    public void testSerialize() {
        CountryDTO countryDTO = new CountryDTO();
        String str = JSON.toJSONString(countryDTO);
        System.out.println(str);
    }
}

Error when running 空指针:

picture

null pointer

From the error message, it can be seen that the method was executed during the serialization process isChinaName(). At this time, this.countrythe variable is empty, so the problem arises:

  • Why is serialization performed isChinaName()?

  • By extension, what methods will be executed during the serialization process?

Source code analysis

Observe the stack information of the call link through debug

picture

picture

picture

picture

In the call chain, a class ASMSerializer_1_CountryDTO.writeis dynamically generated FastJsonusing technology .asmASMSerializer_1_CountryDTO

One of the usage scenarios of asm technology is to dynamically generate classes to replace javareflection, so as to avoid reflection overhead during repeated execution

JavaBeanSerizlier serialization principle

It can be seen from the figure below that in the process of serialization, the method JavaBeanSerializerof the class is mainly called write().

picture

ObjectSerializer implementation class JavaBeanSerializer

It JavaBeanSerializeris mainly obtained through getObjectWriter()methods, through getObjectWriter()debugging the execution process, to find the more critical com.alibaba.fastjson.serializer.SerializeConfig#createJavaBeanSerializermethods, and then to find 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")){
         //省略部分代码...
        }
    }
}

From the code, it can be roughly divided into three situations:

  • @JSONField(.serialize = false, name = "xxx")annotation

  • getXxx() : The method starting with get

  • isXxx(): method starting with is

Serialization flowchart

picture

Serialization flowchart

A front-end and back-end separated blog based on Spring Boot + MyBatis Plus + Vue 3.2 + Vite + Element Plus, including a background management system that supports functions such as articles, categories, tag management, and dashboards.

  • GitHub address: https://github.com/weiwosuoai/WeBlog

  • Gitee Address: https://gitee.com/AllenJiang/WeBlog

 
 

sample code

/**
 * case1: @JSONField(serialize = false)
 * case2: getXxx()返回值为void
 * case3: isXxx()返回值不等于布尔类型
 * case4: @JSONType(ignores = "xxx")
 */
@JSONType(ignores = "otherName")
public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    public static void queryCountryList() {
        System.out.println("queryCountryList()执行!!");
    }

    public Boolean isChinaName() {
        System.out.println("isChinaName()执行!!");
        return true;
    }

    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
     */
    public void getEnglishName3() {
        System.out.println("getEnglishName3()执行!!");
    }

    /**
     * case3: isXxx()返回值不等于布尔类型
     */
    public String isChinaName2() {
        System.out.println("isChinaName2()执行!!");
        return "isChinaName2";
    }
}

The result of the operation is:

isChinaName()执行!!
getEnglishName()执行!!
{"chinaName":true,"englishName":"lucy"}

code specification

It can be seen that there are still many serialization rules. For example, sometimes you need to pay attention to the return value, sometimes you need to pay attention to the number of parameters, sometimes you need to pay attention to @JSONTypeannotations, and sometimes you need to pay attention @JSONFieldto annotations; The degree of mastery of knowledge points is different, and this variance can easily lead to code problems, so try to have a recommended solution.

It is recommended to use @JSONField(serialize = false)to explicitly mark methods that do not participate in serialization. The following is @JSONFieldthe code after using annotations. Can you see at a glance which methods do not need to participate in serialization.

public class CountryDTO {
    private String country;

    public void setCountry(String country) {
        this.country = country;
    }

    public String getCountry() {
        return this.country;
    }

    @JSONField(serialize = false)
    public static void queryCountryList() {
        System.out.println("queryCountryList()执行!!");
    }

    public Boolean isChinaName() {
        System.out.println("isChinaName()执行!!");
        return true;
    }

    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";
    }
}

Three cases of high frequency serialization

picture

Three cases of high frequency serialization

The above process is basically followed, finding problems --> principle analysis --> solving problems --> sublimation (programming specification).

  • Focusing on business: solving problems -> how to choose a good solution -> how to expand n system applications with a good solution;

  • Focus on technology: solve a single problem, and master the principles of this line along a single problem.

But in fact, I am not satisfied with this code, because it depends too much on FastJson. The effect I want is not to depend on any particular JSON serialization framework. When I need to replace it, I can replace it at any time.

And when writing code, don't rely too much on logs. You only need to log important and critical information, not all logs. I have seen a management system that runs full of 128G disks in one hour. There is almost no concurrency, but almost every request outputs several M logs. I will talk about this separately later.

Regarding feature annotations such as @JSONFieldand @JSONType, I will standardize and give a new decoupling solution within the team later, and remove them.

 
 

 

 

Guess you like

Origin blog.csdn.net/2301_78588786/article/details/131870419