ASM在FastJson中的應用

語言: CN / TW / HK

前言

上文ASM入門篇中除了對ASM的使用做了介紹,同時也提到ASM被使用的一些場景,其中有一項就是ASM被用來代替Java反射;FastJson作為序列化工具,就使用了ASM來代替反射的使用,提高整體的效能。

序列化

序列化就是將物件轉換成Json格式的字串,然後用來持久化或者網路傳輸;FastJson提供了介面類ObjectSerializer

public interface ObjectSerializer {
    
    void write(JSONSerializer serializer, //
               Object object, //
               Object fieldName, //
               Type fieldType, //
               int features) throws IOException;
}
  • serializer:序列化上下文;
  • object:需要轉換為Json的物件;
  • fieldName:父物件欄位名稱;
  • fieldType:父物件欄位型別;
  • features:父物件欄位序列化features;

ObjectSerializer的實現類有多種,最常見就是序列化JavaBeanJavaBeanSerializer,當然還有一些其他型別的序列化工具類比如:基本型別、日期型別、列舉型別、一些特殊型別等等;引入的ASM正是對JavaBeanSerializer的優化,當然要使用ASM優化也是有條件的,滿足條件的情況下會通過ASM生成JavaBeanSerializer的子類,代替裡面的反射操作;

啟用條件

FastJson提供了ASM的開關,預設開啟狀態,同時在滿足一定的條件下才能使用ASMSerializerFactory建立ASMSerializer

開關

SerializeConfig中提供了asm開關標識:

private boolean asm = !ASMUtils.IS_ANDROID

預設安卓環境下為false,否則為true,所有服務端開發一般都是開啟asm的;

JSONType註解

  • 如果配置了serializer,則使用配置的序列化類:
@JSONType(serializer = JavaBeanSerializer.class)
  • 如果配置了asmfalse,則不啟用asm
@JSONType(asm = false)
  • 如果配置了指定幾種SerializerFeature,則不開啟asm
@JSONType(serialzeFeatures = {SerializerFeature.WriteNonStringValueAsString,SerializerFeature.WriteEnumUsingToString,SerializerFeature.NotWriteDefaultValue,SerializerFeature.BrowserCompatible})
  • 如果配置了SerializeFilter,則不開啟asm
@JSONType(serialzeFilters = {})

BeanType類資訊

如果類修飾符不為public,則不開啟asm,直接使用JavaBeanSerializer

如果類名稱字元中包含了<001 || >177 || ==.的字元,則不啟用asm

如果類是介面類則不啟用asm

對類屬性的檢查,包括型別,返回值,註解等,不符合的情況下同樣不啟用asm

建立ASMSerializer

通過ASM為每個JavaBean生成一個獨立的JavaBeanSerializer子類,具體步驟如下:

生成類名

建立類之前需要生成一個唯一的名稱:

String className = "ASMSerializer_" + seed.incrementAndGet() + "_" + clazz.getSimpleName()

這裡的seed是一個AtomicLong變數;以Person為例,則生成的classNameASMSerializer_1_Person

生成子類

通過ASM的ClassWriter來生成JavaBeanSerializer的子類,重寫write方法,JavaBeanSerializer中的write方法會使用反射從JavaBean中獲取相關資訊,而通過ASM生成的ASMSerializer_1_Person,是針對Person獨有的序列化工具類,可以看部分程式碼:

public class ASMSerializer_1_Person
extends JavaBeanSerializer
implements ObjectSerializer {
    public ASMSerializer_1_Person(SerializeBeanInfo serializeBeanInfo) {
        super(serializeBeanInfo);
    }

    public void write(JSONSerializer jSONSerializer, Object object, Object object2, Type type, int n) throws IOException {
        if (object == null) {
            jSONSerializer.writeNull();
            return;
        }
        SerializeWriter serializeWriter = jSONSerializer.out;
        if (!this.writeDirect(jSONSerializer)) {
            this.writeNormal(jSONSerializer, object, object2, type, n);
            return;
        }
        if (serializeWriter.isEnabled(32768)) {
            this.writeDirectNonContext(jSONSerializer, object, object2, type, n);
            return;
        }
        Person person = (Person)object;
        if (this.writeReference(jSONSerializer, object, n)) {
            return;
        }
        if (serializeWriter.isEnabled(0x200000)) {
            this.writeAsArray(jSONSerializer, object, object2, type, n);
            return;
        }
        SerialContext serialContext = jSONSerializer.getContext();
        jSONSerializer.setContext(serialContext, object, object2, 0);
        int n2 = 123;
        String string = "email";
        String string2 = person.getEmail();
        if (string2 == null) {
            if (serializeWriter.isEnabled(132)) {
                serializeWriter.write(n2);
                serializeWriter.writeFieldNameDirect(string);
                serializeWriter.writeNull(0, 128);
                n2 = 44;
            }
        } else {
            serializeWriter.writeFieldValueStringWithDoubleQuoteCheck((char)n2, string, string2);
            n2 = 44;
        }
        string = "id";
        int n3 = person.getId();
        serializeWriter.writeFieldValue((char)n2, string, n3);
        n2 = 44;
        string = "name";
        string2 = person.getName();
        if (string2 == null) {
            if (serializeWriter.isEnabled(132)) {
                serializeWriter.write(n2);
                serializeWriter.writeFieldNameDirect(string);
                serializeWriter.writeNull(0, 128);
                n2 = 44;
            }
        } else {
            serializeWriter.writeFieldValueStringWithDoubleQuoteCheck((char)n2, string, string2);
            n2 = 44;
        }
        if (n2 == 123) {
            serializeWriter.write(123);
        }
        serializeWriter.write(125);
        jSONSerializer.setContext(serialContext);
    }
    ...省略...
}

因為是僅是Person的序列化工具,所有可以發現裡面直接強轉ObjectPerson,通過直接呼叫的方式獲取Person的相關資訊,替換了反射的使用,我們知道直接呼叫的效能比使用反射強很多;

檢視原始碼

通過ASM生成的JavaBeanSerializer子類,轉換成位元組陣列通過類載入直接載入到記憶體中,如果想檢視自動生成的類原始碼可以使用如下兩種方式來獲取:

  • 新增Debug程式碼 在ASMSerializerFactory中找到createJavaBeanSerializer方法,ASM生成的程式碼最終會生成位元組陣列,部分程式碼如下所示:

    byte[] code = cw.toByteArray();
    Class<?> serializerClass = classLoader.defineClassPublic(classNameFull, code, 0, code.length);
    

    在IDEA環境下可以在第二行處加斷點,然後右擊斷點,選擇More,勾選Evaluate and log,輸入如下程式碼:

    FileOutputStream fileOutputStream = new FileOutputStream(new File("F:/ASMSerializer_1_Person.class"));
    fileOutputStream.write(code);
    fileOutputStream.close();
    
  • 使用arthas 因為我們已經知道自動生成的類名,可以使用arthas監控當前程序,然後使用jad命令獲取類原始碼:

    [[email protected]]$ jad com.alibaba.fastjson.serializer.ASMSerializer_1_Person
    
    ClassLoader:
    [email protected]
      [email protected]
        [email protected]
    
    Location:
    /D:/myRepository/com/alibaba/fastjson/1.2.70/fastjson-1.2.70.jar
    
    /*
     * Decompiled with CFR.
     *
     * Could not load the following classes:
     *  com.fastjson.Person
     */
    package com.alibaba.fastjson.serializer;
    
    import com.alibaba.fastjson.serializer.JSONSerializer;
    import com.alibaba.fastjson.serializer.JavaBeanSerializer;
    import com.alibaba.fastjson.serializer.ObjectSerializer;
    import com.alibaba.fastjson.serializer.SerialContext;
    import com.alibaba.fastjson.serializer.SerializeBeanInfo;
    import com.alibaba.fastjson.serializer.SerializeWriter;
    import com.fastjson.Person;
    import java.io.IOException;
    import java.lang.reflect.Type;
    
    public class ASMSerializer_1_Person
    extends JavaBeanSerializer
    implements ObjectSerializer {
        public ASMSerializer_1_Person(SerializeBeanInfo serializeBeanInfo) {
    

    注意這裡需要提供類的全名稱:包名+類名

載入類

通過ASM生成的類資訊,不能直接使用,還需要通過類載入載入,這裡通過ASMClassLoader來載入,載入完之後獲取構造器Constructor,然後使用newInstance建立一個JavaBeanSerializer的子類:

byte[] code = cw.toByteArray();
Class<?> serializerClass = classLoader.defineClassPublic(classNameFull, code, 0, code.length);
Constructor<?> constructor = serializerClass.getConstructor(SerializeBeanInfo.class);
Object instance = constructor.newInstance(beanInfo);

反序列化

Json串轉化成物件的過程,FastJson提供了ObjectDeserializer介面類:

public interface ObjectDeserializer {
    <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName);
    int getFastMatchToken();
}
  • parser:反序列化上下文DefaultJSONParser;
  • type:要反序列化的物件的型別;
  • fieldName:父物件欄位名稱;

同序列化過程,大致分為以下幾個過程:

生成類名

生成一個業務類的反序列化工具類名:

String className = "FastjsonASMDeserializer_" + seed.incrementAndGet() + "_" + clazz.getSimpleName();

同樣以Person為例,那麼生成的classNameFastjsonASMDeserializer_1_Person

生成子類

同樣使用ASMClassWriter生成JavaBeanDeserializer的子類,重寫其中的deserialze方法,部分程式碼如下:

public class FastjsonASMDeserializer_1_Person
extends JavaBeanDeserializer {
    public char[] name_asm_prefix__ = "\"name\":".toCharArray();
    public char[] id_asm_prefix__ = "\"id\":".toCharArray();
    public char[] email_asm_prefix__ = "\"email\":".toCharArray();
    public ObjectDeserializer name_asm_deser__;
    public ObjectDeserializer email_asm_deser__;

    public FastjsonASMDeserializer_1_Person(ParserConfig parserConfig, JavaBeanInfo javaBeanInfo) {
        super(parserConfig, javaBeanInfo);
    }

    public Object createInstance(DefaultJSONParser defaultJSONParser, Type type) {
        return new Person();
    }

    public Object deserialze(DefaultJSONParser defaultJSONParser, Type type, Object object, int n) {
        block18: {
            String string;
            int n2;
            String string2;
            int n3;
            Person person;
            block20: {
                ParseContext parseContext;
                ParseContext parseContext2;
                block19: {
                    block22: {
                        JSONLexerBase jSONLexerBase;
                        block24: {
                            int n4;
                            int n5;
                            block23: {
                                block21: {
                                    String string3;
                                    String string4;
                                    jSONLexerBase = (JSONLexerBase)defaultJSONParser.lexer;
                                    if (jSONLexerBase.token() == 14 && jSONLexerBase.isEnabled(n, 8192)) {
                                        return this.deserialzeArrayMapping(defaultJSONParser, type, object, null);
                                    }
                                    if (!jSONLexerBase.isEnabled(512) || jSONLexerBase.scanType("com.fastjson.Person") == -1) break block18;
                                    ParseContext parseContext3 = defaultJSONParser.getContext();
                                    n5 = 0;
                                    person = new Person();
                                    parseContext2 = defaultJSONParser.getContext();
                                    parseContext = defaultJSONParser.setContext(parseContext2, person, object);
                                    if (jSONLexerBase.matchStat == 4) break block19;
                                    n4 = 0;
                                    n3 = 0;
                                    boolean bl = jSONLexerBase.isEnabled(4096);
                                    if (bl) {
                                        n3 |= 1;
                                        string4 = jSONLexerBase.stringDefaultValue();
                                    } else {
                                        string4 = null;
                                    }
                                    string2 = string4;
                                    n2 = 0;
                                    if (bl) {
                                        n3 |= 4;
                                        string3 = jSONLexerBase.stringDefaultValue();
                                    } else {
                                        string3 = null;
                                    }
                                    string = string3;
                                    string2 = jSONLexerBase.scanFieldString(this.email_asm_prefix__);
                                    if (jSONLexerBase.matchStat > 0) {
                                        n3 |= 1;
                                    }
                                    if ((n4 = jSONLexerBase.matchStat) == -1) break block20;
                                    if (jSONLexerBase.matchStat <= 0) break block21;
                                    ++n5;
                                    if (jSONLexerBase.matchStat == 4) break block22;
                                }
                                n2 = jSONLexerBase.scanFieldInt(this.id_asm_prefix__);
                                if (jSONLexerBase.matchStat > 0) {
                                    n3 |= 2;
                                }
                                if ((n4 = jSONLexerBase.matchStat) == -1) break block20;
                                if (jSONLexerBase.matchStat <= 0) break block23;
                                ++n5;
                                if (jSONLexerBase.matchStat == 4) break block22;
                            }
                            string = jSONLexerBase.scanFieldString(this.name_asm_prefix__);
                            if (jSONLexerBase.matchStat > 0) {
                                n3 |= 4;
                            }
                            if ((n4 = jSONLexerBase.matchStat) == -1) break block20;
                            if (jSONLexerBase.matchStat <= 0) break block24;
                            ++n5;
                            if (jSONLexerBase.matchStat == 4) break block22;
                        }
                        if (jSONLexerBase.matchStat != 4) break block20;
                    }
                    if ((n3 & 1) != 0) {
                        person.setEmail(string2);
                    }
                    if ((n3 & 2) != 0) {
                        person.setId(n2);
                    }
                    if ((n3 & 4) != 0) {
                        person.setName(string);
                    }
                }
                defaultJSONParser.setContext(parseContext2);
                if (parseContext != null) {
                    parseContext.object = person;
                }
                return person;
            }
            if ((n3 & 1) != 0) {
                person.setEmail(string2);
            }
            if ((n3 & 2) != 0) {
                person.setId(n2);
            }
            if ((n3 & 4) != 0) {
                person.setName(string);
            }
            return (Person)this.parseRest(defaultJSONParser, type, object, person, n, new int[]{n3});
        }
        return super.deserialze(defaultJSONParser, type, object, n);
    }
	...省略...
}

可以發現同樣使用具體的業務類,代替了在JavaBeanDeserializer使用反射操作;同樣最後需要對該類資訊進行載入,然後通過構造器例項化具體業務物件。

總結

本文使用ASM代替反射只是眾多使用場景的一種,在FastJson中只用了ASM的生成類功能;ASM更強的的功能是它的轉換功能,對現有類進行改造,生成新的類從而對現有類進行功能增強,當然載入的過程也不是簡單的使用類載入器就行了,這時候配合Java Agent來實現熱更新;其實代替反射除了使用ASM,在Protobuf中也提供了一種方法,在研發階段就把所有的業務序列化反序列化操作都準備好,是一種靜態的處理方式,而ASM這種動態生成的方式更加人性化。