preface

In addition to the introduction to ASM, there are several scenarios in which ASM is used. One of them is that ASM is used instead of Java reflection. FastJson as a serialization tool uses ASM instead of reflection to improve overall performance.

serialization

Serialization is converting an object to a JSON-formatted string for persistence or network transmission. FastJson provides the ObjectSerializer interface class:

public interface ObjectSerializer {
    
    void write(JSONSerializer serializer, //
               Object object, //
               Object fieldName, //
               Type fieldType, //
               int features) throws IOException;
}
Copy the code
  • Serializer: serialization context;
  • Object: the object to be converted to Json.
  • FieldName: parent object fieldName;
  • FieldType: parent object fieldType;
  • Features: The parent object field serializes features;

ObjectSerializer is a serialization tool for JavaBeanSerializer. The most common type of ObjectSerializer is the JavaBeanSerializer. There are also some other types of serializer tools, such as basic types, date types, enumerated types, and special types. The introduction of ASM is the optimization of JavaBeanSerializer, of course, the use of ASM optimization is conditional, meet the conditions will be generated through ASM subclass JavaBeanSerializer, instead of reflection operation;

Enabling conditions

FastJson provides the ASM function, which is enabled by default. You can use ASMSerializerFactory to create ASMSerializer only when certain conditions are met.

switch

The ASM switch identifier is provided in SerializeConfig:

private booleanasm = ! ASMUtils.IS_ANDROIDCopy the code

The default value is false for Android, otherwise it is true. All server development is generally enabled with ASM.

JSONType annotations

  • If configuredserializer, the configured serialization class is used:
@JSONType(serializer = JavaBeanSerializer.class)
Copy the code
  • If configuredasmforfalseIs disabledasm:
@JSONType(asm = false)
Copy the code
  • If several types are specifiedSerializerFeature, is not enabledasm:
@JSONType(serialzeFeatures = {SerializerFeature.WriteNonStringValueAsString,SerializerFeature.WriteEnumUsingToString,SerializerFeature.NotWriteDefaul tValue,SerializerFeature.BrowserCompatible})
Copy the code
  • If configuredSerializeFilter, is not enabledasm:
@JSONType(serialzeFilters = {})
Copy the code

BeanType information

If the class modifier is not public, use JavaBeanSerializer instead of enabling ASM.

If the class name of the character contains < 001 | | > 177 | | = =. Is, asm is not enabled.

If the class is an interface class, do not enable ASM;

Check for class attributes, including type, return value, annotation, etc.

Create ASMSerializer

Use ASM to generate an independent JavaBeanSerializer subclass for each JavaBean. The steps are as follows:

To generate the name of the class

To create a class, generate a unique name:

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

Here the seed is an AtomicLong variable; In the case of Person, the generated className is ASMSerializer_1_Person.

To generate a subclass

Use ASM ClassWriter to subclass JavaBeanSerializer and override the write method. The write method in JavaBeanSerializer uses reflection to fetch information from the JavaBeanSerializer. ASMSerializer_1_Person is a serialization utility class that is unique to 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); }... Omit... }Copy the code

As it is only a serialization tool for Person, it can be found that Object is directly transformed into Person and related information of Person is obtained by direct call instead of reflection. We know that the performance of direct call is much stronger than reflection.

View the source code

The JavaBeanSerializer subclass generated by ASM is converted into a byte array and directly loaded into the memory through class loading. To view the automatically generated class source code, you can use the following two methods:

  • Add Debug code to find the createJavaBeanSerializer method in ASMSerializerFactory. The code generated by ASM will eventually generate a byte array, as shown below:

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

    In the IDEA environment, you can add a breakpoint on the second line, right click on the breakpoint, select More, check Evaluate and log, and enter the following code:

    FileOutputStream fileOutputStream = new FileOutputStream(new File("F:/ASMSerializer_1_Person.class"));
    fileOutputStream.write(code);
    fileOutputStream.close();
    Copy the code
  • Use arthas because we already know the name of the auto-generated class, we can use arthas to monitor the current process and then use jad to get the source code for the class:

    [arthas@17916]$ jad com.alibaba.fastjson.serializer.ASMSerializer_1_Person
    
    ClassLoader:
    +-com.alibaba.fastjson.util.ASMClassLoader@32eebfca
      +-jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
        +-jdk.internal.loader.ClassLoaders$PlatformClassLoader@3688c050
    
    Location:
    /D:/myRepository/com/alibaba/fastjson/1.270./fastjson-1.270..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) {
    Copy the code

    Note that you need to provide the full name of the class: package name + class name

Load class

Class information generated by ASM can not be used directly, but needs to be loaded by class loading. Here, use ASMClassLoader to load the class information. After loading, obtain Constructor, and then use newInstance to create a JavaBeanSerializer subclass.

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

deserialization

FastJson provides the ObjectDeserializer interface class to convert Json strings into objects:

public interface ObjectDeserializer {
    <T> T deserialze(DefaultJSONParser parser, Type type, Object fieldName);
    int getFastMatchToken(a);
}
Copy the code
  • Parser: deserialization context DefaultJSONParser;
  • Type: The type of the object to deserialize;
  • FieldName: parent object fieldName;

The co-serialization process can be roughly divided into the following processes:

To generate the name of the class

Generate a deserialization utility class name for a business class:

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

Using Person as an example, the generated className is FastjsonASMDeserializer_1_Person;

To generate a subclass

Also use ASM ClassWriter to generate JavaBeanDeserializer subclass, rewrite the deserialze method, part of the code is as follows:

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); }... Omit... }Copy the code

You can see that the reflection operation in JavaBeanDeserializer is used instead of using a specific business class. Again, the class information needs to be loaded and the concrete business object instantiated through the constructor.

conclusion

The use of ASM instead of reflection in this article is just one of many usage scenarios. In FastJson, only ASM generation classes are used; ASM’s more powerful function is its conversion function, the existing class transformation, generation of new classes to enhance the existing class function, of course, the loading process is not simple to use the class loader on the line, this time with Java Agent to achieve hot update; In fact, in addition to using ASM instead of reflection, Protobuf also provides a method, in the development stage of all business serialization deserialization operations are ready, is a static processing mode, and ASM this dynamic generation mode is more humane.