`
后来我们都老了
  • 浏览: 33762 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Jackson序列化之自动检测

    博客分类:
  • java
阅读更多

一、背景

今天线上出现了一个问题,使用springMVC RestController接口返回json数据给客户端,发现其中某一个model中的所有属性,被序列化了两遍,并且一次是大写开头,一次是小写,部分结构如下:

                "promotionTags": [
                    {
                        "CornerRadius": 1,
                        "TitleFontSize": 10,
                        "Title": "返券",
                        "TitleColor": "#FF9900",
                        "Transparent": true,
                        "BackgroundColor": "#FF9900",
                        "Border": true,
                        "Transparent": true,
                        "Border": true,
                        "cornerRadius": 1,
                        "titleFontSize": 10,
                        "title": "返券",
                        "titleColor": "#FF9900",
                        "transparent": true,
                        "backgroundColor": "#FF9900",
                        "border": true,
                        "transparent": true,
                        "border": true
                    }
                ]

 model结构如下:

 

 

public class HotelLabelModel implements Serializable {

    private Double CornerRadius;

    private String BorderColor;

    private Integer TitleFontSize;

    private String Title;

    private String TitleColor;

    private Boolean Transparent;

    private String BackgroundColor;

    private Boolean Border;

    getter and setter ...
}

 对springMVC序列化做了简单的配置,如下:

public class HotelMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public HotelMappingJacksonHttpMessageConverter() {
        super();
        this.setSelfConfiguration();
    }

    public HotelMappingJacksonHttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
        this.setSelfConfiguration();
    }

    private void setSelfConfiguration() {
        // 任何属性可见
        super.getObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // 过滤null
        super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

二、验证

问题比较容易还原,简单demo如下:

 

public class JacksonTest {

    public static void main(String[] args) throws JsonProcessingException {
        //name content age
        UserBean userBean = new UserBean("Li Lei", "I am Li Lei", 20);

        //jackson序列化
        ObjectMapper objectMapper = new ObjectMapper();
        //设置任何字段可见
        objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        System.out.println(objectMapper.writeValueAsString(userBean));
    }
}
public class UserBean {

    private String Name;

    private String Content;

    private Integer Age;

    public UserBean(String name, String content, Integer age) {
        Name = name;
        Content = content;
        Age = age;
    }

    getter and setter ...
} 
输出结果:{"Name":"Li Lei","Content":"I am Li Lei","Age":20,"name":"Li Lei","content":"I am Li Lei","age":20}

跟踪源码,在类POJOPropertiesCollector中发现

protected void collectAll(){
        LinkedHashMap<String, POJOPropertyBuilder> props = new LinkedHashMap<String, POJOPropertyBuilder>();

        // First: gather basic data
        _addFields(props);
        _addMethods(props);
       ...
}

 属性序列化会判断属性的访问权限,在此就不大量的贴源码了

protected void _addFields(Map<String, POJOPropertyBuilder> props)
    {
            ...
            // having explicit name means that field is visible; otherwise need to check the rules
            boolean visible = (pn != null);
            if (!visible) {
                visible = _visibilityChecker.isFieldVisible(f);
            }
            ...
    }

 根据get/set方法来序列化

protected void _addMethods(Map<String, POJOPropertyBuilder> props)
    {
        final AnnotationIntrospector ai = _annotationIntrospector;
        
        for (AnnotatedMethod m : _classDef.memberMethods()) {
            
            int argCount = m.getParameterCount();
            if (argCount == 0) { // getters (including 'any getter')
            	_addGetterMethod(props, m, ai);
            } else if (argCount == 1) { // setters
            	_addSetterMethod(props, m, ai);
            } else if (argCount == 2) { // any getter?
                if (ai != null  && ai.hasAnySetterAnnotation(m)) {
                    if (_anySetters == null) {
                        _anySetters = new LinkedList<AnnotatedMethod>();
                    }
                    _anySetters.add(m);
                }
            }
        }
    }

 如下get方法,通过反射拿到方法名,截取get后面的名称toLowerCase后,作为序列化的name

 

public static String okNameForRegularGetter(AnnotatedMethod am, String name,
            boolean stdNaming)
    {
        if (name.startsWith("get")) {
            
            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;
                }
            }
            return stdNaming
                    ? stdManglePropertyName(name, 3)
                    : legacyManglePropertyName(name, 3);
        }
        return null;
    } 
protected static String legacyManglePropertyName(final String basename, final int offset)
    {
        final int end = basename.length();
        if (end == offset) { // empty name, nope
            return null;
        }
        // next check: is the first character upper case? If not, return as is
        char c = basename.charAt(offset);
        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();
    }

 

jackson是根据反射获取model的属性和get/set方法,序列化的顺序为字段、方法(get/set),默认只序列化public修饰的字段和public修饰的get/set方法。

所以上面输出结果输出了两遍的结论是第一遍输出的是属性的序列化内容,第二遍输出的是get方法的序列化内容。

定位到了问题,解决方案就比较简单了,方案有很多,如:换屏蔽掉get/set方法的序列化、换用Google的Gson序列化等等,为了减少风险,我选用了第一种方案。

demo修改如下:

 

public class JacksonTest {

    public static void main(String[] args) throws JsonProcessingException {
        UserBean userBean = new UserBean("Li Lei", "I am Li Lei", 20);

        //jackson序列化
        ObjectMapper objectMapper = new ObjectMapper();
        //屏蔽get方法的序列化
        objectMapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);
        //设置任何属性可见
        objectMapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
        System.out.println(objectMapper.writeValueAsString(userBean));
    }
}
输出结果:{"Name":"Li Lei","Content":"I am Li Lei","Age":20}

 

 

三、解决方案

项目中修改springMVC的配置信息,如下:

 

public class HotelMappingJacksonHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    public HotelMappingJacksonHttpMessageConverter() {
        super();
        this.setSelfConfiguration();
    }

    public HotelMappingJacksonHttpMessageConverter(ObjectMapper objectMapper) {
        super(objectMapper);
        this.setSelfConfiguration();
    }

    private void setSelfConfiguration() {
        // 任何属性可见
        super.getObjectMapper().setVisibility(PropertyAccessor.FIELD, Visibility.ANY);
        // 屏蔽get方法
        super.getObjectMapper().setVisibility(PropertyAccessor.GETTER, Visibility.NONE);
        // 屏蔽null
        super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
    }
}

 预发布环境测试通过,上线。

 

四、总结

jackson默认的检测机制如下:public修饰的字段->public修饰的getter/的setter,在使用jackson作为序列化工具的时候要注意属性和方法的修饰权限。

选型jackson是考虑稳定性和性能的平衡点,Fastjson bug比较多,gson非常强大但效率比较低,如对序列化没有特殊要求尽量选用gson或gson & jackson结合使用。

 

0
0
分享到:
评论

相关推荐

    generator-jhipster-flutter-jdlc:JHipster的Flutter模块。 它将使用Flutter生成移动应用(iOS,Android)

    JSON序列化器/反序列化器Java Jackson类似 语言选择 适用于Android的Java / Kotlin 适用于iOS的Objective C / Swift 简介/环境 开发人员 产品 用于开发和生产配置文件的IntelliJ Config文件 实体生成器 通过...

    generator-jhipster-flutter:JHipster的Flutter模块。 它将使用Flutter生成移动应用(iOS,Android)

    JSON序列化器/反序列化器Java Jackson类似 语言选择 适用于Android的Java / Kotlin 适用于iOS的Objective C / Swift 简介/环境 开发人员 产品 用于开发和生产配置文件的IntelliJ Config文件 实体生成器 通过...

    java开源包1

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包11

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包2

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包3

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包6

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包5

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包10

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包4

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包8

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包7

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包9

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    java开源包101

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    Java资源包01

    nfs-rpc是一个集成了各种知名通信框架的高性能RPC框架,目前其最好的性能为在采用grizzly作为通信框架,采用pb作为序列化/反序列化时,tps为168k次/秒。 其支持的功能主要为: 1、透明的调用远端服务器提供的功能...

    JAVA上百实例源码以及开源项目

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    JAVA上百实例源码以及开源项目源代码

     Java生成密钥、保存密钥的实例源码,通过本源码可以了解到Java如何产生单钥加密的密钥(myKey)、产生双钥的密钥对(keyPair)、如何保存公钥的字节数组、保存私钥到文件privateKey.dat、如何用Java对象序列化保存私钥...

    spring security 参考手册中文版

    32.2.1 Spring安全和CAS交互序列 241 32.3 CAS客户端的配置 244 32.3.1服务票据认证 244 32.3.2单一注销 246 32.3.3使用CAS认证无状态服务 249 配置CAS以获取代理授予票证 249 使用代理票证调用无状态服务 250 32.3....

Global site tag (gtag.js) - Google Analytics