【源码】-Lombok-Jackson序列化属性大小写问题

写在前面

  本文记录一下在项目开发过程中遇到的jackson序列化实体字段大小写不一致的问题。稍不留神,你可能也会遇到。这次,我们从源码的角度剖析一下发生了什么。



一、场景描述

  我有一个朋友(此处手动狗头),他在做一个SpringBoot项目,实体的get/set使用的是Lombok注解。最近有一个需求:要给前端返回一个坐标系的数据。于是,他给返回实体的属性起了个名字,叫 xAxisList,yAxisList(这名字起得不错吧,x轴 y轴)。但是,在自测的时候,发现返回的JSON字段名字变成了xaxisList/yaxisList。咦,咋回事儿?不急,我们先看看现象,然后分析问题,最后找出结论。


二、模拟

SpringBoot默认使用的jackson序列化,这个大家都知道,这里我们直接使用jackson的包进行模拟。

1.环境说明

名称说明
spring-boot-starter-parent2.5.4
jackson2.12.4

2.示例代码

package cn.thinkinjava.main;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import lombok.Getter;
import lombok.Setter;
import org.junit.Test;

import java.util.List;

/**
 * Jackson序列化属性字段问题
 *
 * @author qiuxianbao
 * @date 2025/02/19
 */
public class JacksonSerializeTest {

    @Test
    public void testEntityLombok() throws JsonProcessingException {
        EntityLombok result = new EntityLombok();
        result.setXAxisList(Lists.newArrayList("1", "2", "3"));
        String value = new ObjectMapper().writeValueAsString(result);
        // ? 属性字段 xAxisList 怎么变成小写的了
        // {"xaxisList":["1","2","3"]}
        System.out.println(value);
    }

    @Test
    public void testEntityLombokPublic() throws JsonProcessingException {
        EntityLombokPublic result = new EntityLombokPublic();
        result.setXAxisList(Lists.newArrayList("1", "2", "3"));
        String value = new ObjectMapper().writeValueAsString(result);
        // ? 怎么变成2个了
        // {"xAxisList":["1","2","3"],"xaxisList":["1","2","3"]}
        System.out.println(value);
    }


    @Test
    public void testEntity() throws JsonProcessingException {
        Entity result = new Entity();
        result.setxAxisList(Lists.newArrayList("1", "2", "3"));
        String value = new ObjectMapper().writeValueAsString(result);
        // OK
        // {"xAxisList":["1","2","3"]}
        System.out.println(value);
    }

    /**
     * 使用lombok生成get/set首字母是大写的
     */
    @Getter
    @Setter
    private class EntityLombok {
        private List<String> xAxisList;

        // 反编译
        // public List<String> getXAxisList() {
        //     return this.xAxisList;
        // }

        // public void setXAxisList(final List<String> xAxisList) {
        //     this.xAxisList = xAxisList;
        // }
    }

    /**
     * 属性修饰符是public
     */
    @Getter
    @Setter
    private class EntityLombokPublic {
        public List<String> xAxisList;
    }

    /**
     * 通过idea生成的get/set,首字母是小写的
     */
    private class Entity {
        private List<String> xAxisList;

        public List<String> getxAxisList() {
            return xAxisList;
        }

        public void setxAxisList(List<String> xAxisList) {
            this.xAxisList = xAxisList;
        }
    }

}

3.现象

从上面的例子中,我们看到了3个问题:
(1)使用lombok的@Getter和@Setter,属性字段 xAxisList变成小写的了。
(2)xAxisList属性的权限修饰符从private修改成public,属性字段变成2个,还多出来1个。
(3)使用idea自动生成的get/set符合预期。

经过对比,我们发现,get/set有猫腻。

三、源码分析

核心代码只有一行,new ObjectMapper().writeValueAsString(result);
这里我们用 IDE debug看一下testEntityLombok()这个方法,

以下是线程栈的调用链:
writeValueAsString-》_writeValueAndClose

1.writeValueAndClose

protected final void _writeValueAndClose(JsonGenerator g, Object value)
    throws IOException
{
    SerializationConfig cfg = getSerializationConfig();
    if (cfg.isEnabled(SerializationFeature.CLOSE_CLOSEABLE) && (value instanceof Closeable)) {
        _writeCloseable(g, value, cfg);
        return;
    }
    try {
		// 创建provider结合generator生成器,序列化值
        _serializerProvider(cfg).serializeValue(g, value);
    } catch (Exception e) {
        ClassUtil.closeOnFailAndThrowAsIOE(g, e);
        return;
    }
    g.close();
}

以下是线程栈的调用链:
_writeValueAndClose -》serializeValue

2.serializeValue

public void serializeValue(JsonGenerator gen, Object value) throws IOException
{
    _generator = gen;
    if (value == null) {
        _serializeNull(gen);
        return;
    }
    final Class<?> cls = value.getClass();
    // true, since we do want to cache root-level typed serializers (ditto for null property)
    // 创建序列化器 ser
    final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);
    PropertyName rootName = _config.getFullRootName();
    if (rootName == null) { // not explicitly specified
        if (_config.isEnabled(SerializationFeature.WRAP_ROOT_VALUE)) {
            _serialize(gen, value, ser, _config.findRootName(cls));
            return;
        }
    } else if (!rootName.isEmpty()) {
        _serialize(gen, value, ser, rootName);
        return;
    }
	// 序列化操作
    _serialize(gen, value, ser);
}

以下是线程栈的调用链:
serializeValue -》findTypedValueSerializer-》findValueSerializer-》_createAndCacheUntypedSerializer-》_createUntypedSerializer-》createSerializer-》_createSerializer2

_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) {
			// 此处会调用collectAll
            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.
	   			// 此处会调用constructBeanOrAddOnSerializer
                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;
}

3.collectAll

这里是逻辑处理的核心。

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
    // 会删除掉 private 修饰符的属性,以get/set为准
	_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);
    }

    // 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 use custom naming strategy, if applicable...
    // As per [databind#2979], should be AFTER trimming
    PropertyNamingStrategy naming = _findNamingStrategy();
    if (naming != null) {
        _renameUsing(props, naming);
    }

    // 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

protected void _addFields(Map<String, POJOPropertyBuilder> props)
{
    final AnnotationIntrospector ai = _annotationIntrospector;
    /* 28-Mar-2013, tatu: For deserialization we may also want to remove
     *   final fields, as often they won't make very good mutators...
     *   (although, maybe surprisingly, JVM _can_ force setting of such fields!)
     */
    final boolean pruneFinalFields = !_forSerialization && !_config.isEnabled(MapperFeature.ALLOW_FINAL_FIELDS_AS_MUTATORS);
    final boolean transientAsIgnoral = _config.isEnabled(MapperFeature.PROPAGATE_TRANSIENT_MARKER);

    for (AnnotatedField f : _classDef.fields()) {
        // @JsonKey?
        if (Boolean.TRUE.equals(ai.hasAsKey(_config, f))) {
            if (_jsonKeyAccessors == null) {
                _jsonKeyAccessors = new LinkedList<>();
            }
            _jsonKeyAccessors.add(f);
        }
        // @JsonValue?
        if (Boolean.TRUE.equals(ai.hasAsValue(f))) {
            if (_jsonValueAccessors == null) {
                _jsonValueAccessors = new LinkedList<>();
            }
            _jsonValueAccessors.add(f);
            continue;
        }
        // 12-October-2020, dominikrebhan: [databind#1458] Support @JsonAnyGetter on
        //   fields and allow @JsonAnySetter to be declared as well.
        boolean anyGetter = Boolean.TRUE.equals(ai.hasAnyGetter(f));
        boolean anySetter = Boolean.TRUE.equals(ai.hasAnySetter(f));
        if (anyGetter || anySetter) {
            // @JsonAnyGetter?
            if (anyGetter) {
                if (_anyGetterField == null) {
                    _anyGetterField = new LinkedList<>();
                }
                _anyGetterField.add(f);
            }
            // @JsonAnySetter?
            if (anySetter) {
                if (_anySetterField == null) {
                    _anySetterField = new LinkedList<>();
                }
                _anySetterField.add(f);
            }
            continue;
        }
        String implName = ai.findImplicitPropertyName(f);
        if (implName == null) {
            implName = f.getName();
        }
        // 27-Aug-2020, tatu: [databind#2800] apply naming strategy for
        //   fields too, to allow use of naming conventions.
        implName = _accessorNaming.modifyFieldName(f, implName);
        if (implName == null) {
            continue;
        }

        final PropertyName implNameP = _propNameFromSimple(implName);
        // [databind#2527: Field-based renaming can be applied early (here),
        // or at a later point, but probably must be done before pruning
        // final fields. So let's do it early here
        final PropertyName rename = ai.findRenameByField(_config, f, implNameP);
        if ((rename != null) && !rename.equals(implNameP)) {
            if (_fieldRenameMappings == null) {
                _fieldRenameMappings = new HashMap<>();
            }
            _fieldRenameMappings.put(rename, implNameP);
        }

        PropertyName pn;

        if (_forSerialization) {
            // 18-Aug-2011, tatu: As per existing unit tests, we should only
            //   use serialization annotation (@JsonSerialize) when serializing
            //   fields, and similarly for deserialize-only annotations... so
            //   no fallbacks in this particular case.
            pn = ai.findNameForSerialization(f);
        } else {
            pn = ai.findNameForDeserialization(f);
        }
        boolean hasName = (pn != null);
        boolean nameExplicit = hasName;

        if (nameExplicit && pn.isEmpty()) { // empty String meaning "use default name", here just means "same as field name"
            pn = _propNameFromSimple(implName);
            nameExplicit = false;
        }
        // having explicit name means that field is visible; otherwise need to check the rules
        boolean visible = (pn != null);
        if (!visible) {
            visible = _visibilityChecker.isFieldVisible(f);
        }
        // and finally, may also have explicit ignoral
        boolean ignored = ai.hasIgnoreMarker(f);

        // 13-May-2015, tatu: Moved from earlier place (AnnotatedClass) in 2.6
        if (f.isTransient()) {
            // 20-May-2016, tatu: as per [databind#1184] explicit annotation should override
            //    "default" `transient`
            if (!hasName) {
                visible = false;
                if (transientAsIgnoral) {
                    ignored = true;
                }
            }
        }
        /* [databind#190]: this is the place to prune final fields, if they are not
         *  to be used as mutators. Must verify they are not explicitly included.
         *  Also: if 'ignored' is set, need to include until a later point, to
         *  avoid losing ignoral information.
         */
        if (pruneFinalFields && (pn == null) && !ignored
                && Modifier.isFinal(f.getModifiers())) {
            continue;
        }
        _property(props, implName).addField(f, pn, nameExplicit, visible, ignored);
    }
}

* _addMethods

protected void _addMethods(Map<String, POJOPropertyBuilder> props)
{
    for (AnnotatedMethod m : _classDef.memberMethods()) {
        // For methods, handling differs between getters and setters; and
        // we will also only consider entries that either follow the bean
        // naming convention or are explicitly marked: just being visible
        // is not enough (unlike with fields)

        int argCount = m.getParameterCount();
        if (argCount == 0) { // getters (including 'any getter')
	  		// 添加get
            _addGetterMethod(props, m, _annotationIntrospector);
        } else if (argCount == 1) { // setters
	 		// 添加set
            _addSetterMethod(props, m, _annotationIntrospector);
        } else if (argCount == 2) { // any getter?
            if (Boolean.TRUE.equals(_annotationIntrospector.hasAnySetter(m))) {
                if (_anySetters == null) {
                    _anySetters = new LinkedList<AnnotatedMethod>();
                }
                _anySetters.add(m);
            }
        }
    }
}
_addGetterMethod
protected void _addGetterMethod(Map<String, POJOPropertyBuilder> props,
        AnnotatedMethod m, AnnotationIntrospector ai)
{
    
     ...
String implName; // from naming convention
    boolean visible;

    PropertyName pn = ai.findNameForSerialization(m);
    boolean nameExplicit = (pn != null);

    if (!nameExplicit) { // no explicit name; must consider implicit
        implName = ai.findImplicitPropertyName(m);
        if (implName == null) {
			// 查找get的名称
            implName = _accessorNaming.findNameForRegularGetter(m, m.getName());
        }
        if (implName == null) { // if not, must skip
            implName = _accessorNaming.findNameForIsGetter(m, m.getName());
            if (implName == null) {
                return;
            }
            visible = _visibilityChecker.isIsGetterVisible(m);
        } else {
            visible = _visibilityChecker.isGetterVisible(m);
        }
    } else { // explicit indication of inclusion, but may be empty
        // we still need implicit name to link with other pieces
        implName = ai.findImplicitPropertyName(m);
        if (implName == null) {
            implName = _accessorNaming.findNameForRegularGetter(m, m.getName());
            if (implName == null) {
                implName = _accessorNaming.findNameForIsGetter(m, m.getName());
            }
        }
        // if not regular getter name, use method name as is
        if (implName == null) {
            implName = m.getName();
        }
        if (pn.isEmpty()) {
            // !!! TODO: use PropertyName for implicit names too
            pn = _propNameFromSimple(implName);
            nameExplicit = false;
        }
        visible = true;
    }
    // 27-Dec-2019, tatu: [databind#2527] may need to rename according to field
    implName = _checkRenameByField(implName);
    boolean ignore = ai.hasIgnoreMarker(m);
    _property(props, implName).addGetter(m, pn, nameExplicit, visible, ignore);
}
findNameForRegularGetter
public String findNameForRegularGetter(AnnotatedMethod am, String name)
{
    if ((_getterPrefix != null) && name.startsWith(_getterPrefix)) {
        // 16-Feb-2009, tatu: To handle [JACKSON-53], need to block CGLib-provided
        // method "getCallbacks". Not sure of exact safe criteria to get decent
        // coverage without false matches; but for now let's assume there is
        // no reason to use any such getter from CGLib.
        if ("getCallbacks".equals(name)) {
            if (_isCglibGetCallbacks(am)) {
                return null;
            }
        } else if ("getMetaClass".equals(name)) {
            // 30-Apr-2009, tatu: Need to suppress serialization of a cyclic reference
            if (_isGroovyMetaClassGetter(am)) {
                return null;
            }
        }
		// 是否严格遵循标准Bean命名规范,false
        return _stdBeanNaming
                ? stdManglePropertyName(name, _getterPrefix.length())
                : legacyManglePropertyName(name, _getterPrefix.length());
    }
    return null;
}
DefaultAccessorNamingStrategy
protected DefaultAccessorNamingStrategy(MapperConfig<?> config, AnnotatedClass forClass,
        String mutatorPrefix, String getterPrefix, String isGetterPrefix,
        BaseNameValidator baseNameValidator)
{
    _config = config;
    _forClass = forClass;

	// 默认配置
    _stdBeanNaming = config.isEnabled(MapperFeature.USE_STD_BEAN_NAMING);
    _mutatorPrefix = mutatorPrefix;
    _getterPrefix = getterPrefix;
    _isGetterPrefix = isGetterPrefix;
    _baseNameValidator = baseNameValidator;
}
MapperFeature.USE_STD_BEAN_NAMING

看到这里,答案马上就要浮出水面了。Jackson在处理Java Bean属性名称时,默认的机制和严格遵循Bean命名规范机制略有区别,具体区别我们接着往下看代码。

/**
 * Feature that may be enabled to enforce strict compatibility with
 * Bean name introspection, instead of slightly different mechanism
 * Jackson defaults to.
 * Specific difference is that Jackson always lower cases leading upper-case
 * letters, so "getURL()" becomes "url" property; whereas standard Bean
 * naming <b>only</b> lower-cases the first letter if it is NOT followed by
 * another upper-case letter (so "getURL()" would result in "URL" property).
 *<p>
 * Feature is disabled by default for backwards compatibility purposes: earlier
 * Jackson versions used Jackson's own mechanism.
 *
 * @since 2.5
 */
USE_STD_BEAN_NAMING(false),
* legacyManglePropertyName

代码逻辑很简单,至此,你也就能理解为什么getXAxisList就变成了xaxisList,如果是getURL,也就会变成url

protected String legacyManglePropertyName(final String basename, final int offset)
{
    final int end = basename.length();
    if (end == offset) { // empty name, nope
        return null;
    }
	//  获取get后的首字母
    char c = basename.charAt(offset);
    // 12-Oct-2020, tatu: Additional configurability; allow checking that
    //    base name is acceptable (currently just by checking first character)
    if (_baseNameValidator != null) {
        if (!_baseNameValidator.accept(c, basename, offset)) {
            return null;
        }
    }

    // next check: is the first character upper case? If not, return as is
	// 首字母转小写
    char d = Character.toLowerCase(c);
    // 已经是小写,直接返回后面内容
    if (c == d) {
        return basename.substring(offset);
    }
    // otherwise, lower case initial chars. Common case first, just one char
    StringBuilder sb = new StringBuilder(end - offset);
	// 不是小写,追加小写的首字符
    sb.append(d);
	// 从下一个字符开始
    int i = offset+1;
    for (; i < end; ++i) {
		// 取出当前字符
        c = basename.charAt(i);
		// 转小写
        d = Character.toLowerCase(c);
        if (c == d) {
			// 是小写,直接返回后面内容
            sb.append(basename, i, end);
            break;
        }
        // 追加成小写
        sb.append(d);
    }
    return sb.toString();
}

* _removeUnwantedProperties

此处会把private权限修饰的移除掉

protected void _removeUnwantedProperties(Map<String, POJOPropertyBuilder> props)
{
    Iterator<POJOPropertyBuilder> it = props.values().iterator();
    while (it.hasNext()) {
        POJOPropertyBuilder prop = it.next();

        // First: if nothing visible, just remove altogether
		// 不可见
        if (!prop.anyVisible()) {
            it.remove();
            continue;
        }
        // Otherwise, check ignorals
        if (prop.anyIgnorals()) {
            // first: if one or more ignorals, and no explicit markers, remove the whole thing
            if (!prop.isExplicitlyIncluded()) {
                it.remove();
                _collectIgnorals(prop.getName());
                continue;
            }
            // otherwise just remove ones marked to be ignored
            prop.removeIgnored();
            if (!prop.couldDeserialize()) {
                _collectIgnorals(prop.getName());
            }
        }
    }
}

4.constructBeanOrAddOnSerializer

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

        final SerializationConfig config = prov.getConfig();
		// 创建builder
        BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
        builder.setConfig(config);

        // First: any detectable (auto-detect, annotations) properties to serialize?
		// 查找bean的属性
        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?
        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 {
	 		// build
            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;
    }

以下是线程栈的调用链:
constructBeanOrAddOnSerializer-》findBeanProperties

findBeanProperties

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

以下是线程栈的调用链:
findBeanProperties-》removeIgnorableTypes

* removeIgnorableTypes

protected void removeIgnorableTypes(SerializationConfig config, BeanDescription beanDesc,
        List<BeanPropertyDefinition> properties)
{
    AnnotationIntrospector intr = config.getAnnotationIntrospector();
    HashMap<Class<?>,Boolean> ignores = new HashMap<Class<?>,Boolean>();
    Iterator<BeanPropertyDefinition> it = properties.iterator();
    while (it.hasNext()) {
        BeanPropertyDefinition property = it.next();
		// 如果找不到对应的get/属性,就会移除掉
        AnnotatedMember accessor = property.getAccessor();
        /* 22-Oct-2016, tatu: Looks like this removal is an important part of
         *    processing, as taking it out will result in a few test failures...
         *    But should probably be done somewhere else, not here?
         */
        if (accessor == null) {
            it.remove();
            continue;
        }
        Class<?> type = property.getRawPrimaryType();
        Boolean result = ignores.get(type);
        if (result == null) {
            // 21-Apr-2016, tatu: For 2.8, can specify config overrides
            result = config.getConfigOverride(type).getIsIgnoredType();
            if (result == null) {
                BeanDescription desc = config.introspectClassAnnotations(type);
                AnnotatedClass ac = desc.getClassInfo();
                result = intr.isIgnorableType(ac);
                // default to false, non-ignorable
                if (result == null) {
                    result = Boolean.FALSE;
                }
            }
            ignores.put(type, result);
        }
        // lotsa work, and yes, it is ignorable type, so:
        if (result.booleanValue()) {
            it.remove();
        }
    }
}

public AnnotatedMember getAccessor()
{
    AnnotatedMember m = getGetter();
    if (m == null) {
        m = getField();
    }
    return m;
}

四、结论

jackson默认序列化属性字段的处理流程:
1.首先会将【属性字段】添加到集合中
2.去找到【get/set】方法,添加到集合中。以get为例:
去掉get,看首字母是不是小写?
	2.1不是,变成小写; 
		2.1.1 继续处理后面的字母,如果还不是小写,则继续变成小写(一直遇到首字母是小写的,则将前面的小写 + 后面部分返回,添加到集合中)
	2.2是,直接返回去掉get,返回后面的内容,添加到集合中
3.移除掉没有访问权限(不可见),也就是会把private修饰的属性,从集合中删除
4.移除掉不合法的,set方法我随便写行不行?不行,它会先找出get方法,再找属性字段。如果2个都对应不上,则从集合中删除

说明:关于SerializationConfig(序列化配置)、JsonGenerator (生成器)、JsonSerializer(序列化器)部分的内容我们没有展开写,如遇到具体问题,我们再具体分析。

写在后面

  如果本文内容对您有价值或者有启发的话,欢迎点赞、关注、评论和转发。您的反馈和陪伴将促进我们共同进步和成长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值