#JAVA #FastJSON #Jackson #源码分析

# 起因

刚开始是有一个业务需求,需要判断用户手机号是否存在,返回不同的属性,我寻思着就在实体类里写个 getUserPhone 之类的方法,并且为了防止和原本的 getPhone 冲突,特地命名成不一样的名字,但是没想到还是被调用了,虽然猜到这俩 json 库大致是获取了所有的 get 和 set 前缀的方法,然后再进行什么操作,不过还是想知道为什么有这种需求,于是调试下源码记录下。

# 找入口点

业务代码出问题的地方是我进行了一次 JSON.toJSONString () 操作,导致触发了 get 方法,不过我调试过程中发现 FastJSON 获取的 Serializer 是 ASMSerializer,

public final void write(Object object) {  
    if (object == null) {  
        out.writeNull();  
        return;    }  
  
    Class<?> clazz = object.getClass();  
    ObjectSerializer writer = getObjectWriter(clazz);  // ASMSerializer
  
    try {  
        writer.write(this, object, null, null, 0);  
    } catch (IOException e) {  
        throw new JSONException(e.getMessage(), e);  
    }  
}

并且这个类的 write 方法我跟不进去,所以只好换成 jackson。找入口点的方式很简单,既然已经知道会调用 get 方法,那就只需要在 get 方法处打一个断点即可。值得一提的是,这个方法必须要有返回值,否则的话会序列化时就不会去调用这个方法。
触发断点之后,只需要去看调用堆栈,一步一步往上找就可以了。

可以看到,invoke 就是通过反射去调用 get 方法了,而上一层的 serializeAsField 自然是已经获取到 get 方法了。

/**  
 * Method called to access property that this bean stands for, from within * given bean, and to serialize it as a JSON Object field using appropriate * serializer. */@Override  
public void serializeAsField(Object bean, JsonGenerator gen,  
        SerializerProvider prov) throws Exception {  
    // inlined 'get()'  
    final Object value = (_accessorMethod == null) ? _field.get(bean)  
            : _accessorMethod.invoke(bean, (Object[]) null);  
    //...... 省略以下代码
}

在这里, _accessorMethod 也可以通过 idea 看到内容是什么:

那么我们就需要找到 _accessorMethod 是如何被设置的,这里需要在字段上面设置一个断点,类型为监视修改。

经过调试之后找到了相关代码:

/**  
 * @since 2.9 (added `includeInViews` since 2.8)  
 */@SuppressWarnings("unchecked")  
public BeanPropertyWriter(BeanPropertyDefinition propDef,  
        AnnotatedMember member, Annotations contextAnnotations,  
        JavaType declaredType,  
        JsonSerializer<?> ser, TypeSerializer typeSer, JavaType serType,  
        boolean suppressNulls, Object suppressableValue,  
        Class<?>[] includeInViews)  
{  
    // ......
  
    if (member instanceof AnnotatedField) {  
        _accessorMethod = null;  
        _field = (Field) member.getMember();  
    } else if (member instanceof AnnotatedMethod) {  
        _accessorMethod = (Method) member.getMember();  // 这里就是被赋值的地方
        _field = null;  
    } else {  
        // ......
    }  
    // ......
}

member 是通过参数传进来的,继续通过调用堆栈往上找。跟了一路,进入了一个 findBeanProperties 方法,

/**  
 * Method used to collect all actual serializable properties. * Can be overridden to implement custom detection schemes. */protected List<BeanPropertyWriter> findBeanProperties(SerializerProvider prov,  
        BeanDescription beanDesc, BeanSerializerBuilder builder)  
    throws JsonMappingException  
{  
    List<BeanPropertyDefinition> properties = beanDesc.findProperties();  
    final SerializationConfig config = prov.getConfig();  
  
    // ignore specified types  
    removeIgnorableTypes(config, beanDesc, properties);  
    // and possibly remove ones without matching mutator...  
    if (config.isEnabled(MapperFeature.REQUIRE_SETTERS_FOR_GETTERS)) {  
        removeSetterlessGetters(config, beanDesc, properties);  
    }  
    // nothing? can't proceed (caller may or may not throw an exception)  
    if (properties.isEmpty()) {  
        return null;  
    }  
    // null is for value type serializer, which we don't have access to from here (ditto for bean prop)  
    boolean staticTyping = usesStaticTyping(config, beanDesc, null);  
    PropertyBuilder pb = constructPropertyBuilder(config, beanDesc);  
    ArrayList<BeanPropertyWriter> result = new ArrayList<BeanPropertyWriter>(properties.size());  
    for (BeanPropertyDefinition property : properties) {  
        final AnnotatedMember accessor = property.getAccessor();  
        // Type id? Requires special handling:  
        if (property.isTypeId()) {  
            if (accessor != null) {  
                builder.setTypeId(accessor);  
            }  
            continue;  
        }  
        // suppress writing of back references  
        AnnotationIntrospector.ReferenceProperty refType = property.findReferenceType();  
        if (refType != null && refType.isBackReference()) {  
            continue;  
        }  
        if (accessor instanceof AnnotatedMethod) {  
            result.add(_constructWriter(prov, property, pb, staticTyping, (AnnotatedMethod) accessor));  
        } else {  
            result.add(_constructWriter(prov, property, pb, staticTyping, (AnnotatedField) accessor));  
        }  
    }  
    return result;  
}

就如同名字所描述的作用一样,该方法从 beanDesc 这个参数中获取 properties,也就是实体类的属性。

既然 beanDesc 也是传进来的,就继续往上找。最终来到了 beanDesc 获取的地方:

/**  
 * Main serializer constructor method. We will have to be careful * with respect to ordering of various method calls: essentially * we want to reliably figure out which classes are standard types, * and which are beans. The problem is that some bean Classes may * implement standard interfaces (say, {@link java.lang.Iterable}.  
 *<p>  
 * Note: sub-classes may choose to complete replace implementation,  
 * if they want to alter priority of serializer lookups. */@Override  
@SuppressWarnings("unchecked")  
public JsonSerializer<Object> createSerializer(SerializerProvider prov,  
        JavaType origType)  
    throws JsonMappingException  
{  
    // Very first thing, let's check if there is explicit serializer annotation:  
    final SerializationConfig config = prov.getConfig();  
    BeanDescription beanDesc = config.introspect(origType);  
    JsonSerializer<?> ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo());  
    if (ser != null) {  
        return (JsonSerializer<Object>) ser;  
    }  
    boolean staticTyping;  
    // Next: we may have annotations that further indicate actual type to use (a super type)  
    final AnnotationIntrospector intr = config.getAnnotationIntrospector();  
    JavaType type;  
  
    if (intr == null) {  
        type = origType;  
    } else {  
        try {  
            type = intr.refineSerializationType(config, beanDesc.getClassInfo(), origType);  
        } catch (JsonMappingException e) {  
            return prov.reportBadTypeDefinition(beanDesc, e.getMessage());  
        }  
    }  
    if (type == origType) { // no changes, won't force static typing  
        staticTyping = false;  
    } else { // changes; assume static typing; plus, need to re-introspect if class differs  
        staticTyping = true;  
        if (!type.hasRawClass(origType.getRawClass())) {  
            beanDesc = config.introspect(type);  
        }  
    }  
    // Slight detour: do we have a Converter to consider?  
    Converter<Object,Object> conv = beanDesc.findSerializationConverter();  
    if (conv == null) { // no, simple  
        return (JsonSerializer<Object>) _createSerializer2(prov, type, beanDesc, staticTyping);  
    }  
    JavaType delegateType = conv.getOutputType(prov.getTypeFactory());  
    // One more twist, as per [databind#288]; probably need to get new BeanDesc  
    if (!delegateType.hasRawClass(type.getRawClass())) {  
        beanDesc = config.introspect(delegateType);  
        // [#359]: explicitly check (again) for @JsonSerializer...  
        ser = findSerializerFromAnnotation(prov, beanDesc.getClassInfo());  
    }  
    // [databind#731]: Should skip if nominally java.lang.Object  
    if (ser == null && !delegateType.isJavaLangObject()) {  
        ser = _createSerializer2(prov, delegateType, beanDesc, true);  
    }  
    return new StdDelegatingSerializer(conv, delegateType, ser);  
}

在获取 beanDesc 的地方打个断点,跟进去看看到底进行了什么操作。

# 调试

@Override  
public BasicBeanDescription forSerialization(SerializationConfig config,  
        JavaType type, MixInResolver r)  
{  
    // minor optimization: for some JDK types do minimal introspection  
    BasicBeanDescription desc = _findStdTypeDesc(config, type);  
    if (desc == null) {  
        // As per [databind#550], skip full introspection for some of standard  
        // structured types as well        
        desc = _findStdJdkCollectionDesc(config, type);  
        if (desc == null) {  
            desc = BasicBeanDescription.forSerialization(collectProperties(config,  
                    type, r, true));  
        }  
    }  
    return desc;  
}

首先是进行了一些简单的类型判断,比如第一个 _findStdTypeDesc ,判断是否是基本类型,然后获取 BeanDescription。
因为这里是自己定义的实体类,所以进入 collectProperties 方法中。

/**  
 * @since 2.12  
 */protected POJOPropertiesCollector collectProperties(MapperConfig<?> config,  
        JavaType type, MixInResolver r, boolean forSerialization)  
{  
    final AnnotatedClass classDef = _resolveAnnotatedClass(config, type, r);  
    final AccessorNamingStrategy accNaming = type.isRecordType()  
            ? config.getAccessorNaming().forRecord(config, classDef)  
            : config.getAccessorNaming().forPOJO(config, classDef);  
    return constructPropertyCollector(config, classDef, type, forSerialization, accNaming);  
}

这里基本上比较明了了,判断是否是 Record 类型,不是的话走 forPOJO,并且这里还用到了策略模式,而且也提供了接口来重写,也就是说用户可以定义自己的规则,来获取属性。

@Override  
public AccessorNamingStrategy forPOJO(MapperConfig<?> config, AnnotatedClass targetClass)  
{  
    return new DefaultAccessorNamingStrategy(config, targetClass,  
            _setterPrefix, _getterPrefix, _isGetterPrefix,  
            _baseNameValidator);  
}

这里面三个常量 _setterPrefix, _getterPrefix, _isGetterPrefix 分别是 get、set、is,只要是方法前缀带这三个其中之一的,都会被调用。
拿到了正确的策略对象之后,进入 constructPropertyCollector 方法,依旧只是构造一个对象。这里这个对象之后会用来获取实体类的 properties。

/**  
 * Overridable method called for creating {@link POJOPropertiesCollector} instance  
 * to use; override is needed if a custom sub-class is to be used. * * @since 2.12  
 */protected POJOPropertiesCollector constructPropertyCollector(MapperConfig<?> config,  
        AnnotatedClass classDef, JavaType type, boolean forSerialization,  
        AccessorNamingStrategy accNaming)  
{  
    return new POJOPropertiesCollector(config, forSerialization, type, classDef, accNaming);  
}

构造完对象后,通过之前的 forSerialization 方法,创建一个 BasicBeanDescription 对象,不过目前这个对象里面还不存在 properties。

接着回到 createSerializer 方法,继续往下执行:

// ......
// Slight detour: do we have a Converter to consider?  
Converter<Object,Object> conv = beanDesc.findSerializationConverter();  
if (conv == null) { // no, simple  
    return (JsonSerializer<Object>) _createSerializer2(prov, type, beanDesc, staticTyping);  
}
// ......

到这里后,进入 _createSerializer2 方法,

protected JsonSerializer<?> _createSerializer2(SerializerProvider prov,  
        JavaType type, BeanDescription beanDesc, boolean staticTyping)  
    throws JsonMappingException  
{  
    JsonSerializer<?> ser = null;  
    final SerializationConfig config = prov.getConfig();  
    // Container types differ from non-container types  
    // (note: called method checks for module-provided serializers)    if (type.isContainerType()) {  
        if (!staticTyping) {  
            staticTyping = usesStaticTyping(config, beanDesc, null);  
        }  
        // 03-Aug-2012, tatu: As per [databind#40], may require POJO serializer...  
        ser =  buildContainerSerializer(prov, type, beanDesc, staticTyping);  
        // Will return right away, since called method does post-processing:  
        if (ser != null) {  
            return ser;  
        }  
    } else {  
        if (type.isReferenceType()) {  
            ser = findReferenceSerializer(prov, (ReferenceType) type, beanDesc, staticTyping);  
        } else {  
            // Modules may provide serializers of POJO types:  
            for (Serializers serializers : customSerializers()) {  
                ser = serializers.findSerializer(config, type, beanDesc);  
                if (ser != null) {  
                    break;  
                }  
            }  
        }  
        // 25-Jun-2015, tatu: Then JsonSerializable, @JsonValue etc. NOTE! Prior to 2.6,  
        //    this call was BEFORE custom serializer lookup, which was wrong.        if (ser == null) {  
            ser = findSerializerByAnnotations(prov, type, beanDesc);  
        }  
    }  
    if (ser == null) {  
        // Otherwise, we will check "primary types"; both marker types that  
        // indicate specific handling (JsonSerializable), or main types that have        // precedence over container types        ser = findSerializerByLookup(type, config, beanDesc, staticTyping);  
        if (ser == null) {  
            ser = findSerializerByPrimaryType(prov, type, beanDesc, staticTyping);  
            if (ser == null) {  
                // And this is where this class comes in: if type is not a  
                // known "primary JDK type", perhaps it's a bean? We can still                // get a null, if we can't find a single suitable bean property.                ser = findBeanOrAddOnSerializer(prov, type, beanDesc, staticTyping);  
                // 18-Sep-2014, tatu: Actually, as per [jackson-databind#539], need to get  
                //   'unknown' serializer assigned earlier, here, so that it gets properly                //   post-processed                if (ser == null) {  
                    ser = prov.getUnknownTypeSerializer(beanDesc.getBeanClass());  
                }  
            }  
        }  
    }  
    if (ser != null) {  
        // [databind#120]: Allow post-processing  
        if (_factoryConfig.hasSerializerModifiers()) {  
            for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {  
                ser = mod.modifySerializer(config, beanDesc, ser);  
            }  
        }  
    }  
    return ser;  
}

前面一堆 findSerializer 直接跳过,到最后的

ser = findBeanOrAddOnSerializer(prov, type, beanDesc, staticTyping);

至于为啥,因为之前在调用堆栈里见过,一定会走这个方法的。

/**  
 * Method that will try to construct a {@link BeanSerializer} for  
 * given class if at least one property is found, OR, if not, * one of add-on types. *<p>  
 * NOTE: behavior changed a bit  
 */public JsonSerializer<Object> findBeanOrAddOnSerializer(SerializerProvider prov, JavaType type,  
        BeanDescription beanDesc, boolean staticTyping)  
    throws JsonMappingException  
{  
    // First things first: we know some types are not beans...  
    if (!isPotentialBeanType(type.getRawClass())) {  
        // 03-Aug-2012, tatu: Except we do need to allow serializers for Enums,  
        //   as per [databind#24], [databind#2576]        if (!ClassUtil.isEnumType(type.getRawClass())) {  
            return null;  
        }  
    }  
    return constructBeanOrAddOnSerializer(prov, type, beanDesc, staticTyping);  
}

可以看到最后又对 Bean 进行一次构造,继续跟进去:

/**  
     * Method called to construct serializer for serializing specified bean type if     * (but only if, as of 2.10), at least one property is found.     ** @since 2.10  
     */    @SuppressWarnings("unchecked")  
    protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvider prov,  
            JavaType type, BeanDescription beanDesc, boolean staticTyping)  
        throws JsonMappingException  
    {  
        // 13-Oct-2010, tatu: quick sanity check: never try to create bean serializer for plain Object  
        // 05-Jul-2012, tatu: ... but we should be able to just return "unknown type" serializer, right?        if (beanDesc.getBeanClass() == Object.class) {  
            return prov.getUnknownTypeSerializer(Object.class);  
//            throw new IllegalArgumentException("Cannot create bean serializer for Object.class");  
        }  
        JsonSerializer<?> ser = _findUnsupportedTypeSerializer(prov, type, beanDesc);  
        if (ser != null) {  
            return (JsonSerializer<Object>) ser;  
        }  
        // 02-Sep-2021, tatu: [databind#3244] Should not try "proper" serialization of  
        //      things like ObjectMapper, JsonParser or JsonGenerator...        if (_isUnserializableJacksonType(prov, type)) {  
            return new ToEmptyObjectSerializer(type);  
        }  
        final SerializationConfig config = prov.getConfig();  
        BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);  
        builder.setConfig(config);  
  
        // First: any detectable (auto-detect, annotations) properties to serialize?  
        List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder);  
        if (props == null) {  
            props = new ArrayList<BeanPropertyWriter>();  
        } else {  
            props = removeOverlappingTypeIds(prov, beanDesc, builder, props);  
        }  
        // [databind#638]: Allow injection of "virtual" properties:  
        prov.getAnnotationIntrospector().findAndAddVirtualProperties(config, beanDesc.getClassInfo(), props);  
  
        // [JACKSON-440] Need to allow modification bean properties to serialize:  
        if (_factoryConfig.hasSerializerModifiers()) {  
            for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {  
                props = mod.changeProperties(config, beanDesc, props);  
            }  
        }  
  
        // Any properties to suppress?  
  
        // 10-Dec-2021, tatu: [databind#3305] Some JDK types need special help        //    (initially, `CharSequence` with its `isEmpty()` default impl)        props = filterUnwantedJDKProperties(config, beanDesc, props);  
        props = filterBeanProperties(config, beanDesc, props);  
  
        // Need to allow reordering of properties to serialize  
        if (_factoryConfig.hasSerializerModifiers()) {  
            for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {  
                props = mod.orderProperties(config, beanDesc, props);  
            }  
        }  
  
        // And if Object Id is needed, some preparation for that as well: better  
        // do before view handling, mostly for the custom id case which needs        // access to a property        builder.setObjectIdWriter(constructObjectIdHandler(prov, beanDesc, props));  
        builder.setProperties(props);  
        builder.setFilterId(findFilterId(config, beanDesc));  
  
        AnnotatedMember anyGetter = beanDesc.findAnyGetter();  
        if (anyGetter != null) {  
            JavaType anyType = anyGetter.getType();  
            // copied from BasicSerializerFactory.buildMapSerializer():  
            JavaType valueType = anyType.getContentType();  
            TypeSerializer typeSer = createTypeSerializer(config, valueType);  
            // last 2 nulls; don't know key, value serializers (yet)  
            // 23-Feb-2015, tatu: As per [databind#705], need to support custom serializers            JsonSerializer<?> anySer = findSerializerFromAnnotation(prov, anyGetter);  
            if (anySer == null) {  
                // TODO: support '@JsonIgnoreProperties' with any setter?  
                anySer = MapSerializer.construct(/* ignored props*/ (Set<String>) null,  
                        anyType, config.isEnabled(MapperFeature.USE_STATIC_TYPING),  
                        typeSer, null, null, /*filterId*/ null);  
            }  
            // TODO: can we find full PropertyName?  
            PropertyName name = PropertyName.construct(anyGetter.getName());  
            BeanProperty.Std anyProp = new BeanProperty.Std(name, valueType, null,  
                    anyGetter, PropertyMetadata.STD_OPTIONAL);  
            builder.setAnyGetter(new AnyGetterWriter(anyProp, anyGetter, anySer));  
        }  
        // Next: need to gather view information, if any:  
        processViews(config, builder);  
  
        // Finally: let interested parties mess with the result bit more...  
        if (_factoryConfig.hasSerializerModifiers()) {  
            for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {  
                builder = mod.updateBuilder(config, beanDesc, builder);  
            }  
        }  
  
        try {  
            ser = builder.build();  
        } catch (RuntimeException e) {  
            return prov.reportBadTypeDefinition(beanDesc, "Failed to construct BeanSerializer for %s: (%s) %s",  
                    beanDesc.getType(), e.getClass().getName(), e.getMessage());  
        }  
        if (ser == null) { // Means that no properties were found  
            // 21-Aug-2020, tatu: Empty Records should be fine tho            if (type.isRecordType()) {  
                return builder.createDummy();  
            }  
  
            // 06-Aug-2019, tatu: As per [databind#2390], we need to check for add-ons here,  
            //    before considering fallbacks            ser = (JsonSerializer<Object>) findSerializerByAddonType(config, type, beanDesc, staticTyping);  
            if (ser == null) {  
                // If we get this far, there were no properties found, so no regular BeanSerializer  
                // would be constructed. But, couple of exceptions.                // First: if there are known annotations, just create 'empty bean' serializer                if (beanDesc.hasKnownClassAnnotations()) {  
                    return builder.createDummy();  
                }  
            }  
        }  
        return (JsonSerializer<Object>) ser;  
    }

在中间可以看到有这么一句:

// First: any detectable (auto-detect, annotations) properties to serialize?  
List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder);

一路跟进去,

/**  
 * Method used to collect all actual serializable properties. * Can be overridden to implement custom detection schemes. */protected List<BeanPropertyWriter> findBeanProperties(SerializerProvider prov,  
        BeanDescription beanDesc, BeanSerializerBuilder builder)  
    throws JsonMappingException  
{  
    List<BeanPropertyDefinition> properties = beanDesc.findProperties();  
    //..... 省略
}
@Override  
public List<BeanPropertyDefinition> findProperties() {  
    return _properties();  
}

来到 BeanDescription 的 _properties() 方法处:

protected List<BeanPropertyDefinition> _properties() {  
    if (_properties == null) {  
        _properties = _propCollector.getProperties();  
    }  
    return _properties;  
}

这里这个 _propCollector 就是在第一次创建 BeanDescription 时设置进去的 POJOPropertiesCollector 对象。
进入 getProperteis 方法,

public List<BeanPropertyDefinition> getProperties() {  
    // make sure we return a copy, so caller can remove entries if need be:  
    Map<String, POJOPropertyBuilder> props = getPropertyMap();  
    return new ArrayList<BeanPropertyDefinition>(props.values());  
}

接着进入 getPropertyMap:

protected Map<String, POJOPropertyBuilder> getPropertyMap() {  
    if (!_collected) {  
        collectAll();  
    }  
    return _properties;  
}

不过在这里发现_collected 是 true,并且_properties 已经被初始化了,不过问题也不大,按照正常逻辑的话,这里_collected 大概是 false 的状态,接着会进入 collectAll 方法。之后在_properties 上打断点,也可以证实这一点。

/**  
 * Internal method that will collect actual property information. * * @since 2.6  
 */protected void collectAll()  
{  
    LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();  
  
    // First: gather basic data  
    _addFields(props); // note: populates _fieldRenameMappings  
    _addMethods(props);  
    // 25-Jan-2016, tatu: Avoid introspecting (constructor-)creators for non-static  
    //    inner classes, see [databind#1502]    if (!_classDef.isNonStaticInnerClass()) {  
        _addCreators(props);  
    }  
  
    // Remove ignored properties, first; this MUST precede annotation merging  
    // since logic relies on knowing exactly which accessor has which annotation    _removeUnwantedProperties(props);  
    // and then remove unneeded accessors (wrt read-only, read-write)  
    _removeUnwantedAccessor(props);  
  
    // Rename remaining properties  
    _renameProperties(props);  
  
    // and now add injectables, but taking care to avoid overlapping ones  
    // via creator and regular properties    _addInjectables(props);  
  
    // then merge annotations, to simplify further processing  
    // 26-Sep-2017, tatu: Before 2.9.2 was done earlier but that prevented some of    //   annotations from getting properly merged    for (POJOPropertyBuilder property : props.values()) {  
        property.mergeAnnotations(_forSerialization);  
    }  
  
    // And use custom naming strategy, if applicable...  
    // 18-Jan-2021, tatu: To be done before trimming, to resolve    //   [databind#3368]    PropertyNamingStrategy naming = _findNamingStrategy();  
    if (naming != null) {  
        _renameUsing(props, naming);  
    }  
  
    // Sort by visibility (explicit over implicit); drop all but first of member  
    // type (getter, setter etc) if there is visibility difference    for (POJOPropertyBuilder property : props.values()) {  
        property.trimByVisibility();  
    }  
  
    // and, if required, apply wrapper name: note, MUST be done after  
    // annotations are merged.    if (_config.isEnabled(MapperFeature.USE_WRAPPER_NAME_AS_PROPERTY_NAME)) {  
        _renameWithWrappers(props);  
    }  
  
    // well, almost last: there's still ordering...  
    _sortProperties(props);  
    _properties = props;  
    _collected = true;  
}

主要逻辑就是在 addFields 方法和 addMethods 方法里面了,因为代码太长,就不放出来了,简单讲一下逻辑。

# _addFields

这个方法的逻辑比较简单,就是将实体类中的所有字段加入到 Map 中,大致形式如下:

name -> {POJOPropertyBuilder@11000} "[Property 'name'; ctors: null, field(s): [field com.example.fastjsondemo.Entity#name][visible=false,ignore=false,explicitName=false], getter(s): null, setter(s): null]"

可以看到,此时 getter 和 setter 都还是 null 的状态。

# _addMethods

这个方法的代码量就比较多了,分步骤来讲:

  1. 通过方法参数数量,判断是 get 方法还是 set 方法,需要注意的是,这里面拿到的是全部的方法,不只是 get 或 set 开头的。
  2. 如果是 get 方法,判断返回值是否为空,为空则直接跳过,这也解释了为什么需要有返回值才会被调用。
    1. 判断注解,@JsonAnyGetter,@JsonKey,@JsonValue,分别使用不同的 LinkedList 存储。
    2. 通过 findNameForRegularGetter,findNameForIsGetter,分别获取 get 开头和 is 开头的方法的后缀,例如 getName,则会获取到 name。
  3. set 方法大致流程如 get 方法。
  4. 获取到名字之后,就将其于 map 中的字段进行匹配,放入对应的 getter 和 setter
    那么问题就来了,假设我只有方法,没有字段呢,也就是我的业务最开始遇到的问题。
    直接进入对应操作的代码中:
protected POJOPropertyBuilder _property(Map<String, POJOPropertyBuilder> props,  
        String implName)  
{  
    POJOPropertyBuilder prop = props.get(implName);  
    if (prop == null) {  
        prop = new POJOPropertyBuilder(_config, _annotationIntrospector, _forSerialization,  
                PropertyName.construct(implName));  
        props.put(implName, prop);  
    }  
    return prop;  
}

这一段的逻辑就是,假设 map 中不存在那个字段,就重新创建一个放进去,这样会发生什么呢,假设我的实体类只有一个 name,和一个 getPassword 方法,那么一旦我将这个实体类直接返回给前端,那么前端会接收到一个 json:

{
    "name":"",
    "password":""
}

password 值自然就是 getPassword 执行出来的结果,如果里面有什么业务逻辑,也会一并执行。
所以实体类中除了对应属性的方法,最好是不要放其他 is,get,set 开头的方法。