写在前面
本文记录一下在项目开发过程中遇到的jackson序列化实体字段大小写不一致的问题
。稍不留神,你可能也会遇到。这次,我们从源码的角度剖析一下发生了什么。
目录
一、场景描述
我有一个朋友(此处手动狗头),他在做一个SpringBoot项目,实体的get/set使用的是Lombok注解
。最近有一个需求:要给前端返回一个坐标系的数据。于是,他给返回实体的属性起了个名字,叫 xAxisList,yAxisList
(这名字起得不错吧,x轴 y轴)。但是,在自测的时候,发现返回的JSON字段名字变成了xaxisList/yaxisList
。咦,咋回事儿?不急,我们先看看现象,然后分析问题,最后找出结论。
二、模拟
SpringBoot默认使用的jackson序列化,这个大家都知道,这里我们直接使用jackson的包进行模拟。
1.环境说明
名称 | 说明 |
---|---|
spring-boot-starter-parent | 2.5.4 |
jackson | 2.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(序列化器)部分的内容我们没有展开写,如遇到具体问题,我们再具体分析。
写在后面
如果本文内容对您有价值或者有启发的话,欢迎点赞、关注、评论和转发。您的反馈和陪伴将促进我们共同进步和成长。