Fastjson序列化原理

關鍵字: fastjson stackoverflow
本文使用的版本是 1.2.32

fastjson 是阿里開源的Json格式化工具庫赞弥。在項目中使用了fastjson,然后出現了一個奇怪的bug。程序在序列化的時候遞歸調用了我調用序列化函數的函數苗膝。簡單點說就是序列化中遞歸地調用了自己止毕,最后stackoverflow。

下面是是使用的代碼:

public class Host {
    private String name;
    
    public Host() {}
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public static Host factory(byte [] bytes) {
        return JSON.parseObjec(bytes, Host.class);
    }
    public byte[] getJson() {
        return JSON.toJSONBytes(this);
    }
}

然后在程序中某處使用byte []bytes = host.getJson()吞滞,出現的錯誤大概如下:

java.lang.StackOverflowError
    at com.alibaba.fastjson.serializer.JSONSerializer.setContext(JSONSerializer.java:113)
    at com.alibaba.fastjson.serializer.JSONSerializer.setContext(JSONSerializer.java:109)
    at com.alibaba.fastjson.serializer.ASMSerializer_1_Host.write(Unknown Source)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:275)
    at com.alibaba.fastjson.JSON.toJSONBytes(JSON.java:679)
    at com.alibaba.fastjson.JSON.toJSONBytes(JSON.java:605)
    at com.alibaba.fastjson.JSON.toJSONBytes(JSON.java:598)
    at xxx.Host.getBytes(Host.java:38)
    at com.alibaba.fastjson.serializer.ASMSerializer_1_Host.write(Unknown Source)
    at com.alibaba.fastjson.serializer.JSONSerializer.write(JSONSerializer.java:275)
    at com.alibaba.fastjson.JSON.toJSONBytes(JSON.java:679)
    at com.alibaba.fastjson.JSON.toJSONBytes(JSON.java:605)
    at com.alibaba.fastjson.JSON.toJSONBytes(JSON.java:598)
    at xxx.Host.getBytes(Host.java:38)

分析調用堆棧發(fā)現fastjson在生成的serializer.ASMSerializer_1_Host中調用了Host.getJson()導致了遞歸佑菩。排除自己的錯誤后,就將代碼定位到了fastjson中裁赠,應該是fastjson中出了問題倘待。然后開始調試代碼:

public static byte[] toJSONBytes(Object object, SerializeConfig config, int defaultFeatures, SerializerFeature... features) {
    SerializeWriter out = new SerializeWriter(null, defaultFeatures, features);

    try {
        JSONSerializer serializer = new JSONSerializer(out, config);
        serializer.write(object);
        return out.toBytes(IOUtils.UTF8);
    } finally {
        out.close();
    }
}

按照棧調用順序來看,出錯點應該在serializer.write(object)內部组贺,繼續(xù)深入:

public final void write(Object object) {
    if (object == null) {
        out.writeNull();
        return;
    }

    Class<?> clazz = object.getClass();
    ObjectSerializer writer = getObjectWriter(clazz);

    try {
        writer.write(this, object, null, null, 0);
    } catch (IOException e) {
        throw new JSONException(e.getMessage(), e);
    }
}

這里發(fā)現通過getObjectWriter(clazz)取得了host的writer凸舵,想必就是自動生成的ASMSerializer_1_Host實例。本來想進入writer.write中觀察失尖,沒有源代碼只好放棄啊奄。然后將目標放到getObjectWriter中,看看在writer實例構造過程中能不能找到點線索掀潮。

經過幾層跳轉菇夸,來到了真正的getObjectWriter中:

private ObjectSerializer getObjectWriter(Class<?> clazz, boolean create) {
    ObjectSerializer writer = serializers.get(clazz);

    if (writer == null) {
        try {
            final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
            for (Object o : ServiceLoader.load(AutowiredObjectSerializer.class, classLoader)) {
                if (!(o instanceof AutowiredObjectSerializer)) {
                    continue;
                }

                AutowiredObjectSerializer autowired = (AutowiredObjectSerializer) o;
                for (Type forType : autowired.getAutowiredFor()) {
                    put(forType, autowired);
                }
            }
        } catch (ClassCastException ex) {
            // skip
        }

        writer = serializers.get(clazz);
    }

    if (writer == null) {
        final ClassLoader classLoader = JSON.class.getClassLoader();
        if (classLoader != Thread.currentThread().getContextClassLoader()) {
            try {
                for (Object o : ServiceLoader.load(AutowiredObjectSerializer.class, classLoader)) {

                    if (!(o instanceof AutowiredObjectSerializer)) {
                        continue;
                    }

                    AutowiredObjectSerializer autowired = (AutowiredObjectSerializer) o;
                    for (Type forType : autowired.getAutowiredFor()) {
                        put(forType, autowired);
                    }
                }
            } catch (ClassCastException ex) {
                // skip
            }

            writer = serializers.get(clazz);
        }
    }
    
    if (writer == null) {
        if (Map.class.isAssignableFrom(clazz)) {
            put(clazz, MapSerializer.instance);
        } else if (List.class.isAssignableFrom(clazz)) {
            put(clazz, ListSerializer.instance);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            put(clazz, CollectionCodec.instance);
        } else if (Date.class.isAssignableFrom(clazz)) {
            put(clazz, DateCodec.instance);
        } else if (JSONAware.class.isAssignableFrom(clazz)) {
            put(clazz, JSONAwareSerializer.instance);
        } else if (JSONSerializable.class.isAssignableFrom(clazz)) {
            put(clazz, JSONSerializableSerializer.instance);
        } else if (JSONStreamAware.class.isAssignableFrom(clazz)) {
            put(clazz, MiscCodec.instance);
        } else if (clazz.isEnum() || (clazz.getSuperclass() != null && clazz.getSuperclass().isEnum())) {
            JSONType jsonType = clazz.getAnnotation(JSONType.class);
            if (jsonType != null && jsonType.serializeEnumAsJavaBean()) {
                put(clazz, createJavaBeanSerializer(clazz));
            } else {
                put(clazz, EnumSerializer.instance);
            }
        } else if (clazz.isArray()) {
            Class<?> componentType = clazz.getComponentType();
            ObjectSerializer compObjectSerializer = getObjectWriter(componentType);
            put(clazz, new ArraySerializer(componentType, compObjectSerializer));
        } else if (Throwable.class.isAssignableFrom(clazz)) {
            SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy);
            beanInfo.features |= SerializerFeature.WriteClassName.mask;
            put(clazz, new JavaBeanSerializer(beanInfo));
        } else if (TimeZone.class.isAssignableFrom(clazz) || Map.Entry.class.isAssignableFrom(clazz)) {
            put(clazz, MiscCodec.instance);
        } else if (Appendable.class.isAssignableFrom(clazz)) {
            put(clazz, AppendableSerializer.instance);
        } else if (Charset.class.isAssignableFrom(clazz)) {
            put(clazz, ToStringSerializer.instance);
        } else if (Enumeration.class.isAssignableFrom(clazz)) {
            put(clazz, EnumerationSerializer.instance);
        } else if (Calendar.class.isAssignableFrom(clazz) //
                || XMLGregorianCalendar.class.isAssignableFrom(clazz)) {
            put(clazz, CalendarCodec.instance);
        } else if (Clob.class.isAssignableFrom(clazz)) {
            put(clazz, ClobSeriliazer.instance);
        } else if (TypeUtils.isPath(clazz)) {
            put(clazz, ToStringSerializer.instance);
        } else if (Iterator.class.isAssignableFrom(clazz)) {
            put(clazz, MiscCodec.instance);
        } else {
            String className = clazz.getName();
            if (className.startsWith("java.awt.") //
                && AwtCodec.support(clazz) //
            ) {
                // awt
                if (!awtError) {
                    try {
                        put(Class.forName("java.awt.Color"), AwtCodec.instance);
                        put(Class.forName("java.awt.Font"), AwtCodec.instance);
                        put(Class.forName("java.awt.Point"), AwtCodec.instance);
                        put(Class.forName("java.awt.Rectangle"), AwtCodec.instance);
                    } catch (Throwable e) {
                        awtError = true;
                        // skip
                    }
                }
                return  AwtCodec.instance;
            }
            
            // jdk8
            if ((!jdk8Error) //
                && (className.startsWith("java.time.") //
                    || className.startsWith("java.util.Optional") //
                    || className.equals("java.util.concurrent.atomic.LongAdder")
                    || className.equals("java.util.concurrent.atomic.DoubleAdder")
                )) {
                try {
                    put(Class.forName("java.time.LocalDateTime"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.LocalDate"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.LocalTime"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.ZonedDateTime"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.OffsetDateTime"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.OffsetTime"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.ZoneOffset"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.ZoneRegion"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.Period"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.Duration"), Jdk8DateCodec.instance);
                    put(Class.forName("java.time.Instant"), Jdk8DateCodec.instance);

                    put(Class.forName("java.util.Optional"), OptionalCodec.instance);
                    put(Class.forName("java.util.OptionalDouble"), OptionalCodec.instance);
                    put(Class.forName("java.util.OptionalInt"), OptionalCodec.instance);
                    put(Class.forName("java.util.OptionalLong"), OptionalCodec.instance);

                    put(Class.forName("java.util.concurrent.atomic.LongAdder"), AdderSerializer.instance);
                    put(Class.forName("java.util.concurrent.atomic.DoubleAdder"), AdderSerializer.instance);
                    
                    writer = serializers.get(clazz);
                    if (writer != null) {
                        return writer;
                    }
                } catch (Throwable e) {
                    // skip
                    jdk8Error = true;
                }
            }
            
            if ((!oracleJdbcError) //
                && className.startsWith("oracle.sql.")) {
                try {
                    put(Class.forName("oracle.sql.DATE"), DateCodec.instance);
                    put(Class.forName("oracle.sql.TIMESTAMP"), DateCodec.instance);
                    
                    writer = serializers.get(clazz);
                    if (writer != null) {
                        return writer;
                    }
                } catch (Throwable e) {
                    // skip
                    oracleJdbcError = true;
                }
            }
            
            if ((!springfoxError) //
                && className.equals("springfox.documentation.spring.web.json.Json")) {
                try {
                    put(Class.forName("springfox.documentation.spring.web.json.Json"), //
                        SwaggerJsonSerializer.instance);
                    
                    writer = serializers.get(clazz);
                    if (writer != null) {
                        return writer;
                    }
                } catch (ClassNotFoundException e) {
                    // skip
                    springfoxError = true;
                }
            }

            if ((!guavaError) //
                    && className.startsWith("com.google.common.collect.")) {
                try {
                    put(Class.forName("com.google.common.collect.HashMultimap"), //
                            GuavaCodec.instance);
                    put(Class.forName("com.google.common.collect.LinkedListMultimap"), //
                            GuavaCodec.instance);
                    put(Class.forName("com.google.common.collect.ArrayListMultimap"), //
                            GuavaCodec.instance);
                    put(Class.forName("com.google.common.collect.TreeMultimap"), //
                            GuavaCodec.instance);

                    writer = serializers.get(clazz);
                    if (writer != null) {
                        return writer;
                    }
                } catch (ClassNotFoundException e) {
                    // skip
                    guavaError = true;
                }
            }

            if (className.equals("net.sf.json.JSONNull")) {
                try {
                    put(Class.forName("net.sf.json.JSONNull"), //
                            MiscCodec.instance);
                } catch (ClassNotFoundException e) {
                    // skip
                }
                writer = serializers.get(clazz);
                if (writer != null) {
                    return writer;
                }
            }

            if (TypeUtils.isProxy(clazz)) {
                Class<?> superClazz = clazz.getSuperclass();

                ObjectSerializer superWriter = getObjectWriter(superClazz);
                put(clazz, superWriter);
                return superWriter;
            }

            if (create) {
                put(clazz, createJavaBeanSerializer(clazz));
            }
        }

        writer = serializers.get(clazz);
    }
    return writer;
}

簡單掃描代碼邏輯,發(fā)現writer是通過serializers.get(clazz)獲取的仪吧。而代碼中分別從Thread.currentThread().getContextClassLoader庄新、JSON.class.getClassLoader以及最后對一下常見類分析來填充serializers。最后一種辦法的末尾薯鼠,走到了:

put(clazz, createJavaBeanSerializer(clazz));

可以發(fā)現邏輯是實在找不到择诈,使用createJavaBeanSerializer(clazz)來創(chuàng)建clazz對應的writer〕龌剩看來我們的目標應該是這個createJavaBeanSerializer函數羞芍,所以進一步深入:

private final ObjectSerializer createJavaBeanSerializer(Class<?> clazz) {
    SerializeBeanInfo beanInfo = TypeUtils.buildBeanInfo(clazz, null, propertyNamingStrategy, fieldBased);
    if (beanInfo.fields.length == 0 && Iterable.class.isAssignableFrom(clazz)) {
        return MiscCodec.instance;
    }

    return createJavaBeanSerializer(beanInfo);
}

首先調用TypeUtils.buildBeanInfo來生成SerializerBeanInfo。

public static SerializeBeanInfo buildBeanInfo(Class<?> beanType //
        , Map<String, String> aliasMap //
        , PropertyNamingStrategy propertyNamingStrategy //
        , boolean fieldBased //
) {
    
    JSONType jsonType = beanType.getAnnotation(JSONType.class);

    // fieldName,field 郊艘,先生成fieldName的快照荷科,減少之后的findField的輪詢
    Map<String, Field> fieldCacheMap = new HashMap<String, Field>();
    ParserConfig.parserAllFieldToCache(beanType, fieldCacheMap);

    List<FieldInfo> fieldInfoList = fieldBased
            ? computeGettersWithFieldBase(beanType, aliasMap, false, propertyNamingStrategy) //
            : computeGetters(beanType, jsonType, aliasMap, fieldCacheMap, false, propertyNamingStrategy);
    FieldInfo[] fields = new FieldInfo[fieldInfoList.size()];
    fieldInfoList.toArray(fields);
    
    String[] orders = null;

    final int features;
    String typeName = null;
    if (jsonType != null) {
        orders = jsonType.orders();
        typeName = jsonType.typeName();
        if (typeName.length() == 0) {
            typeName = null;
        }
        features = SerializerFeature.of(jsonType.serialzeFeatures());
    } else {
        features = 0;
    }
    
    FieldInfo[] sortedFields;
    List<FieldInfo> sortedFieldList;
    if (orders != null && orders.length != 0) {
        sortedFieldList = fieldBased
                ? computeGettersWithFieldBase(beanType, aliasMap, true, propertyNamingStrategy) //
                : computeGetters(beanType, jsonType, aliasMap,fieldCacheMap, true, propertyNamingStrategy);
    } else {
        sortedFieldList = new ArrayList<FieldInfo>(fieldInfoList);
        Collections.sort(sortedFieldList);
    }
    sortedFields = new FieldInfo[sortedFieldList.size()];
    sortedFieldList.toArray(sortedFields);
    
    if (Arrays.equals(sortedFields, fields)) {
        sortedFields = fields;
    }
    
    return new SerializeBeanInfo(beanType, jsonType, typeName, features, fields, sortedFields);
}

其中parserAllFieldToCache將字段保存起來,減少訪問次數纱注。緊接著設置fieldInfoList的值畏浆,此時fieldBase為false,所以進入了computeGetters狞贱。

public static List<FieldInfo> computeGetters(Class<?> clazz, //
                                            JSONType jsonType, //
                                            Map<String, String> aliasMap, //
                                            Map<String, Field> fieldCacheMap, //
                                            boolean sorted, //
                                            PropertyNamingStrategy propertyNamingStrategy //
) {
    Map<String, FieldInfo> fieldInfoMap = new LinkedHashMap<String, FieldInfo>();

    for (Method method : clazz.getMethods()) {
        String methodName = method.getName();
        int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
        String label = null;

        if (Modifier.isStatic(method.getModifiers())) {
            continue;
        }

        if (method.getReturnType().equals(Void.TYPE)) {
            continue;
        }

        if (method.getParameterTypes().length != 0) {
            continue;
        }

        if (method.getReturnType() == ClassLoader.class) {
            continue;
        }

        if (method.getName().equals("getMetaClass")
            && method.getReturnType().getName().equals("groovy.lang.MetaClass")) {
            continue;
        }

        JSONField annotation = method.getAnnotation(JSONField.class);

        if (annotation == null) {
            annotation = getSuperMethodAnnotation(clazz, method);
        }

        if (annotation != null) {
            if (!annotation.serialize()) {
                continue;
            }

            ordinal = annotation.ordinal();
            serialzeFeatures = SerializerFeature.of(annotation.serialzeFeatures());
            parserFeatures = Feature.of(annotation.parseFeatures());

            if (annotation.name().length() != 0) {
                String propertyName = annotation.name();

                if (aliasMap != null) {
                    propertyName = aliasMap.get(propertyName);
                    if (propertyName == null) {
                        continue;
                    }
                }

                FieldInfo fieldInfo = new FieldInfo(propertyName, method, null, clazz, null, ordinal,
                                                    serialzeFeatures, parserFeatures, annotation, null, label);
                fieldInfoMap.put(propertyName, fieldInfo);
                continue;
            }

            if (annotation.label().length() != 0) {
                label = annotation.label();
            }
        }

        if (methodName.startsWith("get")) {
            if (methodName.length() < 4) {
                continue;
            }

            if (methodName.equals("getClass")) {
                continue;
            }

            if (methodName.equals("getDeclaringClass") && clazz.isEnum()) {
                continue;
            }

            char c3 = methodName.charAt(3);

            String propertyName;
            if (Character.isUpperCase(c3) //
                || c3 > 512 // for unicode method name
            ) {
            if (compatibleWithJavaBean) {
                    propertyName = decapitalize(methodName.substring(3));
                } else {
                    propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
                }
                propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName,  propertyName,3);
            } else if (c3 == '_') {
                propertyName = methodName.substring(4);
            } else if (c3 == 'f') {
                propertyName = methodName.substring(3);
            } else if (methodName.length() >= 5 && Character.isUpperCase(methodName.charAt(4))) {
                propertyName = decapitalize(methodName.substring(3));
            } else {
                continue;
            }

            boolean ignore = isJSONTypeIgnore(clazz, propertyName);

            if (ignore) {
                continue;
            }
            //假如bean的field很多的情況一下刻获,輪詢時將大大降低效率
            Field field = ParserConfig.getFieldFromCache(propertyName, fieldCacheMap);

            if (field == null && propertyName.length() > 1) {
                char ch = propertyName.charAt(1);
                if (ch >= 'A' && ch <= 'Z') {
                    String javaBeanCompatiblePropertyName = decapitalize(methodName.substring(3));
                    field = ParserConfig.getFieldFromCache(javaBeanCompatiblePropertyName, fieldCacheMap);
                }
            }

            JSONField fieldAnnotation = null;
            if (field != null) {
                fieldAnnotation = field.getAnnotation(JSONField.class);

                if (fieldAnnotation != null) {
                    if (!fieldAnnotation.serialize()) {
                        continue;
                    }

                    ordinal = fieldAnnotation.ordinal();
                    serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                    parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                    if (fieldAnnotation.name().length() != 0) {
                        propertyName = fieldAnnotation.name();

                        if (aliasMap != null) {
                            propertyName = aliasMap.get(propertyName);
                            if (propertyName == null) {
                                continue;
                            }
                        }
                    }

                    if (fieldAnnotation.label().length() != 0) {
                        label = fieldAnnotation.label();
                    }
                }
            }

            if (aliasMap != null) {
                propertyName = aliasMap.get(propertyName);
                if (propertyName == null) {
                    continue;
                }
            }

            if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
            }

            FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                                                annotation, fieldAnnotation, label);
            fieldInfoMap.put(propertyName, fieldInfo);
        }

        if (methodName.startsWith("is")) {
            if (methodName.length() < 3) {
                continue;
            }

            if (method.getReturnType() != Boolean.TYPE
                    && method.getReturnType() != Boolean.class) {
                continue;
            }

            char c2 = methodName.charAt(2);

            String propertyName;
            if (Character.isUpperCase(c2)) {
                if (compatibleWithJavaBean) {
                    propertyName = decapitalize(methodName.substring(2));
                } else {
                    propertyName = Character.toLowerCase(methodName.charAt(2)) + methodName.substring(3);
                }
                propertyName = getPropertyNameByCompatibleFieldName(fieldCacheMap, methodName,  propertyName,2);
            } else if (c2 == '_') {
                propertyName = methodName.substring(3);
            } else if (c2 == 'f') {
                propertyName = methodName.substring(2);
            } else {
                continue;
            }

            boolean ignore = isJSONTypeIgnore(clazz, propertyName);

            if (ignore) {
                continue;
            }

            Field field = ParserConfig.getFieldFromCache(propertyName,fieldCacheMap);

            if (field == null) {
                field = ParserConfig.getFieldFromCache(methodName,fieldCacheMap);
            }

            JSONField fieldAnnotation = null;
            if (field != null) {
                fieldAnnotation = field.getAnnotation(JSONField.class);

                if (fieldAnnotation != null) {
                    if (!fieldAnnotation.serialize()) {
                        continue;
                    }

                    ordinal = fieldAnnotation.ordinal();
                    serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
                    parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

                    if (fieldAnnotation.name().length() != 0) {
                        propertyName = fieldAnnotation.name();

                        if (aliasMap != null) {
                            propertyName = aliasMap.get(propertyName);
                            if (propertyName == null) {
                                continue;
                            }
                        }
                    }

                    if (fieldAnnotation.label().length() != 0) {
                        label = fieldAnnotation.label();
                    }
                }
            }

            if (aliasMap != null) {
                propertyName = aliasMap.get(propertyName);
                if (propertyName == null) {
                    continue;
                }
            }

            if (propertyNamingStrategy != null) {
                propertyName = propertyNamingStrategy.translate(propertyName);
            }

            //優(yōu)先選擇get
            if (fieldInfoMap.containsKey(propertyName)) {
                continue;
            }

            FieldInfo fieldInfo = new FieldInfo(propertyName, method, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                                                annotation, fieldAnnotation, label);
            fieldInfoMap.put(propertyName, fieldInfo);
        }
    }

    Field[] fields = clazz.getFields();
    computeFields(clazz, aliasMap, propertyNamingStrategy, fieldInfoMap, fields);

    return getFieldInfos(clazz, sorted, fieldInfoMap);
}

這里針對clazz的每一個方法進行了判斷,由于只有get和set開頭的函數斥滤,所以只關心methodName.startsWith("get")分支将鸵。最后進入了getPropertyNameByCompatibleFieldName所在的分支勉盅,并將propertyName設置為對應get的屬性名。在getPropertyNameByCompatibleFieldName函數中顶掉,而compatibleWithFieldName設置為false所以相當于跳過了草娜。

private static String getPropertyNameByCompatibleFieldName(Map<String, Field> fieldCacheMap, String methodName,
                                                            String propertyName,int fromIdx) {
    if (compatibleWithFieldName){
            if (!fieldCacheMap.containsKey(propertyName)){
                String tempPropertyName=methodName.substring(fromIdx);
                return  fieldCacheMap.containsKey(tempPropertyName)?tempPropertyName:propertyName;
            }
        }
    return propertyName;
}

繼續(xù)分析,程序進入isJSONTypeIgnore根據注解判斷是否跳過該字段痒筒,我的例子中不關心宰闰。緊接著調用了getFieldFromCache:

public static Field getFieldFromCache(String fieldName, Map<String, Field> fieldCacheMap) {
    Field field = fieldCacheMap.get(fieldName);

    if (field == null) {
        field = fieldCacheMap.get("_" + fieldName);
    }

    if (field == null) {
        field = fieldCacheMap.get("m_" + fieldName);
    }

    if (field == null) {
        char c0 = fieldName.charAt(0);
        if (c0 >= 'a' && c0 <= 'z') {
            char[] chars = fieldName.toCharArray();
            chars[0] -= 32; // lower
            String fieldNameX = new String(chars);
            field = fieldCacheMap.get(fieldNameX);
        }
    }

    return field;
}

這里按照剛才取出的方法名來查找字段,如果失敗則加上或者m之類的方法繼續(xù)判斷簿透。返回繼續(xù)分析移袍,在做了部分如注解別名之類的處理后,將分析得到的結果生成一個FieldInfo老充,并保存在fieldInfoMap中葡盗。最后調用computeFields進一步處理一些public屬性的fields數據。最后經過getFieldInfos處理后啡浊,將得到的List<FieldInfo>中觅够,返回上一級。

private static void computeFields(
        Class<?> clazz, //
        Map<String, String> aliasMap, //
        PropertyNamingStrategy propertyNamingStrategy, //
        Map<String, FieldInfo> fieldInfoMap, //
        Field[] fields) {

    for (Field field : fields) {
        if (Modifier.isStatic(field.getModifiers())) {
            continue;
        }

        JSONField fieldAnnotation = field.getAnnotation(JSONField.class);

        int ordinal = 0, serialzeFeatures = 0, parserFeatures = 0;
        String propertyName = field.getName();
        String label = null;
        if (fieldAnnotation != null) {
            if (!fieldAnnotation.serialize()) {
                continue;
            }

            ordinal = fieldAnnotation.ordinal();
            serialzeFeatures = SerializerFeature.of(fieldAnnotation.serialzeFeatures());
            parserFeatures = Feature.of(fieldAnnotation.parseFeatures());

            if (fieldAnnotation.name().length() != 0) {
                propertyName = fieldAnnotation.name();
            }

            if (fieldAnnotation.label().length() != 0) {
                label = fieldAnnotation.label();
            }
        }

        if (aliasMap != null) {
            propertyName = aliasMap.get(propertyName);
            if (propertyName == null) {
                continue;
            }
        }

        if (propertyNamingStrategy != null) {
            propertyName = propertyNamingStrategy.translate(propertyName);
        }

        if (!fieldInfoMap.containsKey(propertyName)) {
            FieldInfo fieldInfo = new FieldInfo(propertyName, null, field, clazz, null, ordinal, serialzeFeatures, parserFeatures,
                                                null, fieldAnnotation, label);
            fieldInfoMap.put(propertyName, fieldInfo);
        }
    }
}

分析到這里巷嚣,可以發(fā)現在fieldInfoList中實際上值:name,json喘先。看到這里相比也能猜出大概了廷粒,現在繼續(xù)跟蹤窘拯。回到buildBeanInfo中坝茎,將剛才得到的fieldInfoList構造為SerializeBeanInfo并返回涤姊。

public ObjectSerializer createJavaBeanSerializer(SerializeBeanInfo beanInfo) {
    JSONType jsonType = beanInfo.jsonType;
    
    if (jsonType != null) {
        Class<?> serializerClass = jsonType.serializer();
        if (serializerClass != Void.class) {
            try {
                Object seralizer = serializerClass.newInstance();
                if (seralizer instanceof ObjectSerializer) {
                    return (ObjectSerializer) seralizer;
                }
            } catch (Throwable e) {
                // skip
            }
        }
        
        if (jsonType.asm() == false) {
            asm = false;
        }

        for (SerializerFeature feature : jsonType.serialzeFeatures()) {
            if (SerializerFeature.WriteNonStringValueAsString == feature //
                    || SerializerFeature.WriteEnumUsingToString == feature //
                    || SerializerFeature.NotWriteDefaultValue == feature) {
                asm = false;
                break;
            }
        }
    }
    
    Class<?> clazz = beanInfo.beanType;
    if (!Modifier.isPublic(beanInfo.beanType.getModifiers())) {
        return new JavaBeanSerializer(beanInfo);
    }

    boolean asm = this.asm && !fieldBased;

    if (asm && asmFactory.classLoader.isExternalClass(clazz)
            || clazz == Serializable.class || clazz == Object.class) {
        asm = false;
    }

    if (asm && !ASMUtils.checkName(clazz.getSimpleName())) {
        asm = false;
    }
    
    if (asm) {
        for(FieldInfo fieldInfo : beanInfo.fields){
            Field field = fieldInfo.field;
            if (field != null && !field.getType().equals(fieldInfo.fieldClass)) {
                asm = false;
                break;
            }

            Method method = fieldInfo.method;
            if (method != null && !method.getReturnType().equals(fieldInfo.fieldClass)) {
                asm = false;
                break;
            }

            JSONField annotation = fieldInfo.getAnnotation();
            
            if (annotation == null) {
                continue;
            }

            if ((!ASMUtils.checkName(annotation.name())) //
                    || annotation.format().length() != 0
                    || annotation.jsonDirect()
                    || annotation.serializeUsing() != Void.class
                    || annotation.unwrapped()
                    ) {
                asm = false;
                break;
            }

            for (SerializerFeature feature : annotation.serialzeFeatures()) {
                if (SerializerFeature.WriteNonStringValueAsString == feature //
                        || SerializerFeature.WriteEnumUsingToString == feature //
                        || SerializerFeature.NotWriteDefaultValue == feature) {
                    asm = false;
                    break;
                }
            }
        }
    }
    
    if (asm) {
        try {
            ObjectSerializer asmSerializer = createASMSerializer(beanInfo);
            if (asmSerializer != null) {
                return asmSerializer;
            }
        } catch (ClassFormatError e) {
            // skip
        } catch (ClassCastException e) {
            // skip
        } catch (Throwable e) {
            throw new JSONException("create asm serializer error, class "
                    + clazz, e);
        }
    }

    return new JavaBeanSerializer(beanInfo);
}

經過處理后進入了createASMSerializer,其中調用createJavaBeanSerializer來創(chuàng)建具體的writer:

public JavaBeanSerializer createJavaBeanSerializer(SerializeBeanInfo beanInfo) throws Exception {
    Class<?> clazz = beanInfo.beanType;
    if (clazz.isPrimitive()) {
        throw new JSONException("unsupportd class " + clazz.getName());
    }

    JSONType jsonType = clazz.getAnnotation(JSONType.class);

    FieldInfo[] unsortedGetters = beanInfo.fields;;

    for (FieldInfo fieldInfo : unsortedGetters) {
        if (fieldInfo.field == null //
            && fieldInfo.method != null //
            && fieldInfo.method.getDeclaringClass().isInterface()) {
            return new JavaBeanSerializer(clazz);
        }
    }

    FieldInfo[] getters = beanInfo.sortedFields;

    boolean nativeSorted = beanInfo.sortedFields == beanInfo.fields;

    if (getters.length > 256) {
        return new JavaBeanSerializer(clazz);
    }

    for (FieldInfo getter : getters) {
        if (!ASMUtils.checkName(getter.getMember().getName())) {
            return new JavaBeanSerializer(clazz);
        }
    }

    String className = "ASMSerializer_" + seed.incrementAndGet() + "_" + clazz.getSimpleName();
    String packageName = ASMSerializerFactory.class.getPackage().getName();
    String classNameType = packageName.replace('.', '/') + "/" + className;
    String classNameFull = packageName + "." + className;

    ClassWriter cw = new ClassWriter();
    cw.visit(V1_5 //
                , ACC_PUBLIC + ACC_SUPER //
                , classNameType //
                , JavaBeanSerializer //
                , new String[] { ObjectSerializer } //
    );

    for (FieldInfo fieldInfo : getters) {
        if (fieldInfo.fieldClass.isPrimitive() //
            //|| fieldInfo.fieldClass.isEnum() //
            || fieldInfo.fieldClass == String.class) {
            continue;
        }

        new FieldWriter(cw, ACC_PUBLIC, fieldInfo.name + "_asm_fieldType", "Ljava/lang/reflect/Type;") //
                                                                                                        .visitEnd();

        if (List.class.isAssignableFrom(fieldInfo.fieldClass)) {
            new FieldWriter(cw, ACC_PUBLIC, fieldInfo.name + "_asm_list_item_ser_",
                            ObjectSerializer_desc) //
                                                    .visitEnd();
        }

        new FieldWriter(cw, ACC_PUBLIC, fieldInfo.name + "_asm_ser_", ObjectSerializer_desc) //
                                                                                                    .visitEnd();
    }

    MethodVisitor mw = new MethodWriter(cw, ACC_PUBLIC, "<init>", "(" + desc(SerializeBeanInfo.class) + ")V", null, null);
    mw.visitVarInsn(ALOAD, 0);
    mw.visitVarInsn(ALOAD, 1);
    mw.visitMethodInsn(INVOKESPECIAL, JavaBeanSerializer, "<init>", "(" + desc(SerializeBeanInfo.class) + ")V");

    // init _asm_fieldType
    for (int i = 0; i < getters.length; ++i) {
        FieldInfo fieldInfo = getters[i];
        if (fieldInfo.fieldClass.isPrimitive() //
//                || fieldInfo.fieldClass.isEnum() //
            || fieldInfo.fieldClass == String.class) {
            continue;
        }

        mw.visitVarInsn(ALOAD, 0);

        if (fieldInfo.method != null) {
            mw.visitLdcInsn(com.alibaba.fastjson.asm.Type.getType(desc(fieldInfo.declaringClass)));
            mw.visitLdcInsn(fieldInfo.method.getName());
            mw.visitMethodInsn(INVOKESTATIC, type(ASMUtils.class), "getMethodType",
                                "(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/reflect/Type;");

        } else {
            mw.visitVarInsn(ALOAD, 0);
            mw.visitLdcInsn(i);
            mw.visitMethodInsn(INVOKESPECIAL, JavaBeanSerializer, "getFieldType", "(I)Ljava/lang/reflect/Type;");
        }

        mw.visitFieldInsn(PUTFIELD, classNameType, fieldInfo.name + "_asm_fieldType", "Ljava/lang/reflect/Type;");
    }

    mw.visitInsn(RETURN);
    mw.visitMaxs(4, 4);
    mw.visitEnd();

    boolean DisableCircularReferenceDetect = false;
    if (jsonType != null) {
        for (SerializerFeature featrues : jsonType.serialzeFeatures()) {
            if (featrues == SerializerFeature.DisableCircularReferenceDetect) {
                DisableCircularReferenceDetect = true;
                break;
            }
        }
    }

    // 0 write
    // 1 writeNormal
    // 2 writeNonContext
    for (int i = 0; i < 3; ++i) {
        String methodName;
        boolean nonContext = DisableCircularReferenceDetect;
        boolean writeDirect = false;
        if (i == 0) {
            methodName = "write";
            writeDirect = true;
        } else if (i == 1) {
            methodName = "writeNormal";
        } else {
            writeDirect = true;
            nonContext = true;
            methodName = "writeDirectNonContext";
        }

        Context context = new Context(getters, beanInfo, classNameType, writeDirect,
                                        nonContext);

        mw = new MethodWriter(cw, //
                                ACC_PUBLIC, //
                                methodName, //
                                "(L" + JSONSerializer
                                            + ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V", //
                                null, //
                                new String[] { "java/io/IOException" } //
        );

        {
            Label endIf_ = new Label();
            mw.visitVarInsn(ALOAD, Context.obj);
            //serializer.writeNull();
            mw.visitJumpInsn(IFNONNULL, endIf_);
            mw.visitVarInsn(ALOAD, Context.serializer);
            mw.visitMethodInsn(INVOKEVIRTUAL, JSONSerializer,
                    "writeNull", "()V");

            mw.visitInsn(RETURN);
            mw.visitLabel(endIf_);
        }

        mw.visitVarInsn(ALOAD, Context.serializer);
        mw.visitFieldInsn(GETFIELD, JSONSerializer, "out", SerializeWriter_desc);
        mw.visitVarInsn(ASTORE, context.var("out"));

        if ((!nativeSorted) //
            && !context.writeDirect) {

            if (jsonType == null || jsonType.alphabetic()) {
                Label _else = new Label();

                mw.visitVarInsn(ALOAD, context.var("out"));
                mw.visitMethodInsn(INVOKEVIRTUAL, SerializeWriter, "isSortField", "()Z");

                mw.visitJumpInsn(IFNE, _else);
                mw.visitVarInsn(ALOAD, 0);
                mw.visitVarInsn(ALOAD, 1);
                mw.visitVarInsn(ALOAD, 2);
                mw.visitVarInsn(ALOAD, 3);
                mw.visitVarInsn(ALOAD, 4);
                mw.visitVarInsn(ILOAD, 5);
                mw.visitMethodInsn(INVOKEVIRTUAL, classNameType,
                                    "writeUnsorted", "(L" + JSONSerializer
                                                    + ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V");
                mw.visitInsn(RETURN);

                mw.visitLabel(_else);
            }
        }

        // isWriteDoubleQuoteDirect
        if (context.writeDirect && !nonContext) {
            Label _direct = new Label();
            Label _directElse = new Label();

            mw.visitVarInsn(ALOAD, 0);
            mw.visitVarInsn(ALOAD, Context.serializer);
            mw.visitMethodInsn(INVOKEVIRTUAL, JavaBeanSerializer, "writeDirect", "(L" + JSONSerializer + ";)Z");
            mw.visitJumpInsn(IFNE, _directElse);

            mw.visitVarInsn(ALOAD, 0);
            mw.visitVarInsn(ALOAD, 1);
            mw.visitVarInsn(ALOAD, 2);
            mw.visitVarInsn(ALOAD, 3);
            mw.visitVarInsn(ALOAD, 4);
            mw.visitVarInsn(ILOAD, 5);
            mw.visitMethodInsn(INVOKEVIRTUAL, classNameType,
                                "writeNormal", "(L" + JSONSerializer
                                                + ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V");
            mw.visitInsn(RETURN);

            mw.visitLabel(_directElse);
            mw.visitVarInsn(ALOAD, context.var("out"));
            mw.visitLdcInsn(SerializerFeature.DisableCircularReferenceDetect.mask);
            mw.visitMethodInsn(INVOKEVIRTUAL, SerializeWriter, "isEnabled", "(I)Z");
            mw.visitJumpInsn(IFEQ, _direct);

            mw.visitVarInsn(ALOAD, 0);
            mw.visitVarInsn(ALOAD, 1);
            mw.visitVarInsn(ALOAD, 2);
            mw.visitVarInsn(ALOAD, 3);
            mw.visitVarInsn(ALOAD, 4);
            mw.visitVarInsn(ILOAD, 5);
            mw.visitMethodInsn(INVOKEVIRTUAL, classNameType, "writeDirectNonContext",
                                "(L" + JSONSerializer + ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V");
            mw.visitInsn(RETURN);

            mw.visitLabel(_direct);
        }

        mw.visitVarInsn(ALOAD, Context.obj); // obj
        mw.visitTypeInsn(CHECKCAST, type(clazz)); // serializer
        mw.visitVarInsn(ASTORE, context.var("entity")); // obj
        generateWriteMethod(clazz, mw, getters, context);
        mw.visitInsn(RETURN);
        mw.visitMaxs(7, context.variantIndex + 2);
        mw.visitEnd();
    }

    if (!nativeSorted) {
        // sortField support
        Context context = new Context(getters, beanInfo, classNameType, false,
                                        DisableCircularReferenceDetect);

        mw = new MethodWriter(cw, ACC_PUBLIC, "writeUnsorted",
                                "(L" + JSONSerializer + ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V",
                                null, new String[] { "java/io/IOException" });

        mw.visitVarInsn(ALOAD, Context.serializer);
        mw.visitFieldInsn(GETFIELD, JSONSerializer, "out", SerializeWriter_desc);
        mw.visitVarInsn(ASTORE, context.var("out"));

        mw.visitVarInsn(ALOAD, Context.obj); // obj
        mw.visitTypeInsn(CHECKCAST, type(clazz)); // serializer
        mw.visitVarInsn(ASTORE, context.var("entity")); // obj

        generateWriteMethod(clazz, mw, unsortedGetters, context);

        mw.visitInsn(RETURN);
        mw.visitMaxs(7, context.variantIndex + 2);
        mw.visitEnd();
    }

    // 0 writeAsArray
    // 1 writeAsArrayNormal
    // 2 writeAsArrayNonContext
    for (int i = 0; i < 3; ++i) {
        String methodName;
        boolean nonContext = DisableCircularReferenceDetect;
        boolean writeDirect = false;
        if (i == 0) {
            methodName = "writeAsArray";
            writeDirect = true;
        } else if (i == 1) {
            methodName = "writeAsArrayNormal";
        } else {
            writeDirect = true;
            nonContext = true;
            methodName = "writeAsArrayNonContext";
        }

        Context context = new Context(getters, beanInfo, classNameType, writeDirect,
                                        nonContext);

        mw = new MethodWriter(cw, ACC_PUBLIC, methodName,
                                "(L" + JSONSerializer + ";Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/reflect/Type;I)V",
                                null, new String[] { "java/io/IOException" });

        mw.visitVarInsn(ALOAD, Context.serializer);
        mw.visitFieldInsn(GETFIELD, JSONSerializer, "out", SerializeWriter_desc);
        mw.visitVarInsn(ASTORE, context.var("out"));

        mw.visitVarInsn(ALOAD, Context.obj); // obj
        mw.visitTypeInsn(CHECKCAST, type(clazz)); // serializer
        mw.visitVarInsn(ASTORE, context.var("entity")); // obj
        generateWriteAsArray(clazz, mw, getters, context);
        mw.visitInsn(RETURN);
        mw.visitMaxs(7, context.variantIndex + 2);
        mw.visitEnd();
    }

    byte[] code = cw.toByteArray();

    Class<?> exampleClass = classLoader.defineClassPublic(classNameFull, code, 0, code.length);
    Constructor<?> constructor = exampleClass.getConstructor(SerializeBeanInfo.class);
    Object instance = constructor.newInstance(beanInfo);

    return (JavaBeanSerializer) instance;
}

到這里為止景东,我們的分析就可以結束了砂轻,實際上這里是根據fieldInfo,通過CodeGen技術生成一個writer實例斤吐。而getJson被簡單當作了json屬性的getter,所以在writer.write(object)中調用了getJson從而出現了遞歸厨喂。那么這個問題的簡單解決辦法就是將getJson換個名字和措,比如toJson。最后蜕煌,在github的issue中也翻到了一個對應的問題派阱,作者給出的答案就是換個名字。
使用fastjson序列化this的時候棧溢出

原文出處:FastJson 踩坑記錄

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末斜纪,一起剝皮案震驚了整個濱河市贫母,隨后出現的幾起案子文兑,更是在濱河造成了極大的恐慌,老刑警劉巖腺劣,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绿贞,死亡現場離奇詭異,居然都是意外死亡橘原,警方通過查閱死者的電腦和手機籍铁,發(fā)現死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來趾断,“玉大人拒名,你說我怎么就攤上這事∮笞茫” “怎么了增显?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長脐帝。 經常有香客問我甸怕,道長,這世上最難降的妖魔是什么腮恩? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任梢杭,我火速辦了婚禮,結果婚禮上秸滴,老公的妹妹穿的比我還像新娘武契。我一直安慰自己,他們只是感情好荡含,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布咒唆。 她就那樣靜靜地躺著,像睡著了一般释液。 火紅的嫁衣襯著肌膚如雪全释。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天误债,我揣著相機與錄音浸船,去河邊找鬼。 笑死寝蹈,一個胖子當著我的面吹牛李命,可吹牛的內容都是我干的。 我是一名探鬼主播箫老,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼封字,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起阔籽,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤流妻,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后笆制,有當地人在樹林里發(fā)現了一具尸體绅这,經...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年项贺,在試婚紗的時候發(fā)現自己被綠了君躺。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡开缎,死狀恐怖棕叫,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情奕删,我是刑警寧澤俺泣,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布,位于F島的核電站完残,受9級特大地震影響伏钠,放射性物質發(fā)生泄漏。R本人自食惡果不足惜谨设,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一熟掂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扎拣,春花似錦赴肚、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至刊愚,卻和暖如春踊跟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鸥诽。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工商玫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人衙传。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓决帖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蓖捶。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內容