zoukankan      html  css  js  c++  java
  • 【私人定制jackson】定制jackson的自定义序列化(null值的处理)

          最近用springMVC做服务端的http+json的接口,出现一个不是特别容易解决的问题:

    在对List类型的值进行处理时,有一部分服务是有做一些逻辑判断的,在逻辑判断不通过的时候会返回一个null值,

    而有一些值是直接通过jpa查询到的List类型的值则会进行实例化,即同样是List类型,一个是null,一个"[]"。

      最简单的办法是在null值的地方全部实例化一个new ArrayList<?>(0);但是这样会修改很多地方,而且对于这些情况都要进行实例化分配内存不是那么的理想。

      所以就在springMvc转json的地方做手脚。

      我们都知道springMvc是使用jackson做的json序列化工具。  

       @Bean
        public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
            final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();       
            converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
            return converter;
        }

         可以配置其一个MappingJackson2HttpMessageConverter类,这个类同时可以做另一个事情,防止ie对json数据当做文件进行下载。

      MappingJackson2HttpMessageConverter类中可以取到一个ObjectMapper,即jackson序列化的主类。

         查看代码看到:

        

        @Override
        protected void writeInternal(Object object, HttpOutputMessage outputMessage)
                throws IOException, HttpMessageNotWritableException {
    
            JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
            // The following has been deprecated as late as Jackson 2.2 (April 2013);
            // preserved for the time being, for Jackson 2.0/2.1 compatibility.
            @SuppressWarnings("deprecation")
            JsonGenerator jsonGenerator =
                    this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
    
            // A workaround for JsonGenerators not applying serialization features
            // https://github.com/FasterXML/jackson-databind/issues/12
            if (this.objectMapper.isEnabled(SerializationFeature.INDENT_OUTPUT)) {
                jsonGenerator.useDefaultPrettyPrinter();
            }
    
            try {
                if (this.jsonPrefix != null) {
                    jsonGenerator.writeRaw(this.jsonPrefix);
                }
    //此处进行序列化
    this.objectMapper.writeValue(jsonGenerator, object); } catch (JsonProcessingException ex) { throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex); } }

         看到使用了

        this.objectMapper.writeValue(jsonGenerator, object);

      进行序列化,跟进去,看到一句话:

            _serializerProvider(config).serializeValue(jgen, value);

         看来这个就是具体的序列化的方法了。

      

     public void serializeValue(JsonGenerator jgen, Object value)
            throws IOException, JsonGenerationException
        {
            if (value == null) {
                _serializeNull(jgen);
                return;
            }
            Class<?> cls = value.getClass();
            // true, since we do want to cache root-level typed serializers (ditto for null property)
            final JsonSerializer<Object> ser = findTypedValueSerializer(cls, true, null);       try {
                ser.serialize(value, jgen, this);            
            } catch (IOException ioe) { // As per [JACKSON-99], pass IOException and subtypes as-is
                throw ioe;
            } catch (Exception e) { // but wrap RuntimeExceptions, to get path information
                String msg = e.getMessage();
                if (msg == null) {
                    msg = "[no message for "+e.getClass().getName()+"]";
                }
                throw new JsonMappingException(msg, e);
            }
        }

           ok,我们本来的目的是 对null值的处理,那么在这个地方我们看到了一个对null的处理,

      

      /**
         * @since 2.0
         */
        public JsonSerializer<Object> getDefaultNullValueSerializer() {
            return _nullValueSerializer;
        }
      @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonGenerationException
        {
            jgen.writeNull();
        }

        那么,我们是不是只要替换掉这个_nullValueSerializer  就可以了呢,是的,这个一个比较常规的对于null值处理的方法。

      具体参考:http://blog.csdn.net/zshake/article/details/17582691

       但是这个jsonSerializer有一个比较严重的问题,就是这个nullValueSerializer是全局的,即所有的null都会应用这个JsonSerializer,在这个类中无法判断类型。

     我无法判断当我是List类型时怎样,普通类型时怎样。

       所以继续向下跟代码:

            跟入 ser.serialize(value, jgen, this);  这个方法,发现其有许多的实现,通过调试模式,进入了一个叫做BeanSerializer的类,其实现为:

       

     /**
         * Main serialization method that will delegate actual output to
         * configured
         * {@link BeanPropertyWriter} instances.
         */
        @Override
        public final void serialize(Object bean, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonGenerationException
        {
            if (_objectIdWriter != null) {
                _serializeWithObjectId(bean, jgen, provider, true);
                return;
            }
            jgen.writeStartObject();
            if (_propertyFilterId != null) {
                serializeFieldsFiltered(bean, jgen, provider);
            } else {
    //调试模式下最终走了这个方法 serializeFields(bean, jgen, provider); } jgen.writeEndObject(); }

    protected void serializeFields(Object bean, JsonGenerator jgen, SerializerProvider provider)
            throws IOException, JsonGenerationException
        {
            final BeanPropertyWriter[] props;
            if (_filteredProps != null && provider.getActiveView() != null) {
                props = _filteredProps;
            } else {
                props = _props;
            }
            int i = 0;
            try {
                for (final int len = props.length; i < len; ++i) {
                    BeanPropertyWriter prop = props[i];
                    if (prop != null) { // can have nulls in filtered list
                        prop.serializeAsField(bean, jgen, provider);
                    }
                }
                if (_anyGetterWriter != null) {
                    _anyGetterWriter.getAndSerialize(bean, jgen, provider);
                }
            } catch (Exception e) {
                String name = (i == props.length) ? "[anySetter]" : props[i].getName();
                wrapAndThrow(provider, e, bean, name);
            } catch (StackOverflowError e) {
                /* 04-Sep-2009, tatu: Dealing with this is tricky, since we do not
                 *   have many stack frames to spare... just one or two; can't
                 *   make many calls.
                 */
                JsonMappingException mapE = new JsonMappingException("Infinite recursion (StackOverflowError)", e);
                String name = (i == props.length) ? "[anySetter]" : props[i].getName();
                mapE.prependPath(new JsonMappingException.Reference(bean, name));
                throw mapE;
            }
        }

             这个方法中最重要的一个东西就是BeanPropertyWriter 这个类,这个类是由SerializerFactory 工厂进行实例化的,其作用是对bean中的每个字段进行jackson操作的封装,其中封装了字段的一些元信息,

    和对此字段进行jackson序列化的操作,那么问题来了,这么说来,这个BeanPropertyWriter类其实就是jackson真正如何对每个bean进行转json的最终的操作的实现,那么我们是不是只要替换掉这个类就可以了

    呢,答案是肯定的。

      那么看看jackson为我们预留的对此类进行自定义的方法。

      jackson通过JsonSerializer来对javabean序列化,此serializer都是通过一个SerializerFactory活的的,在这个工厂类中,找到了一个这个方法:

      

      @SuppressWarnings("unchecked")
        protected JsonSerializer<Object> constructBeanSerializer(SerializerProvider prov,
                BeanDescription beanDesc)
            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("Can not create bean serializer for Object.class");
            }
            final SerializationConfig config = prov.getConfig();
            BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
            builder.setConfig(config);
            
            // First: any detectable (auto-detect, annotations) properties to serialize?
    //注意这里,这里为每个属性实例化了一个BeanPropertyWriter List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder); if (props == null) { props = new ArrayList<BeanPropertyWriter>(); } // [JACKSON-440] Need to allow modification bean properties to serialize:
    //这里通过_factoryConfig中的配置:BeanSerializerModifier 对这个props做了change(修改), if (_factoryConfig.hasSerializerModifiers()) { for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) { props = mod.changeProperties(config, beanDesc, props); } } // Any properties to suppress? props = filterBeanProperties(config, beanDesc, props);
    //.....之后的省略

              重点注意:      

               //这里通过_factoryConfig中的配置:   BeanSerializerModifier 对这个props做了change(修改),

           if (_factoryConfig.hasSerializerModifiers()) {

             for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()){

                 props = mod.changeProperties(config, beanDesc, props);

             }

           }

         这里从factoryConfig中拿出来了一个Modifiers集合,并且通过这些Modifiers对List<BeanPropertyWriter>进行了修改,那么这样就简单了,我们只要自己定义一个Modifyer对某个List类型的BeanPropertyWriter进行修改集合了。

               首先定义一个Modifyer

         

    public class MyBeanSerializerModifier extends BeanSerializerModifier {
    
        private JsonSerializer<Object> _nullArrayJsonSerializer = new MyNullArrayJsonSerializer();
    
        @Override
        public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
                List<BeanPropertyWriter> beanProperties) {
            // 循环所有的beanPropertyWriter
            for (int i = 0; i < beanProperties.size(); i++) {
                BeanPropertyWriter writer = beanProperties.get(i);
                // 判断字段的类型,如果是array,list,set则注册nullSerializer
                if (isArrayType(writer)) {
    //给writer注册一个自己的nullSerializer writer.assignNullSerializer(
    this.defaultNullArrayJsonSerializer()); } } return beanProperties; } // 判断是什么类型 protected boolean isArrayType(BeanPropertyWriter writer) { Class<?> clazz = writer.getPropertyType(); return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class); } protected JsonSerializer<Object> defaultNullArrayJsonSerializer() { return _nullArrayJsonSerializer; } }

            一个对null值处理的JsonSeralizer:

      

    public class MyNullArrayJsonSerializer extends JsonSerializer<Object> {
    
        @Override
        public void serialize(Object value, JsonGenerator jgen, SerializerProvider provider) throws IOException, JsonProcessingException {
            if (value == null) {
                jgen.writeStartArray();
                jgen.writeEndArray();
            } else {
                jgen.writeObject(value);
            }
        }
    }

        主要是看看怎么设置到jackson里:

      还是那个MappingJackson2HttpMessageConverter:

      

    @Bean
        public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
            final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            ObjectMapper mapper = converter.getObjectMapper();
            // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
            mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));  
    
            converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
            return converter;
        }

      看看效果:

      在设置mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));

         我们转换一个类:

      

    public class NullTest {
    
        private String key;
    
        private List<String> list;
    
        private Map<String, String> map;
    
        private int[] array = new int[0];
    
        private String[] array2;
    
        private java.util.Date now = new java.util.Date();
       
    //getter.....setter.....
    
    }
    @RequestMapping("test/aaa")
        @ResponseBody
        public ResponseResult test() {
            System.err.println("=====");
            return this.successResult().data(new NullTest());
        }

          之前:

              {"success":true,"code":0,"message":"","data":{"key":null,"list":null,"map":null,"array":[],"array2":null,"now":1450167151924}}

         之后:

        {"success":true,"code":0,"message":"","data":{"key":null,"list":[],"map":null,"array":[],"array2":[],"now":1450167205726}}

         成功!

    参考资料:

      http://www.baeldung.com/jackson-json-view-annotation

      http://blog.csdn.net/zshake/article/details/17582691

    ========================华丽的分割线=======================================================

    基于最近好几个人问我如果不用spring-boot,普通的spring-mvc怎么进行xml配置,特此总结一下

    思路是使用spring的定义bean的方式,通过工厂的方式进行定义

    1.首先创建一个工厂:

     

    public class MappingJackson2HttpMessageConverterFactory {
    
        public MappingJackson2HttpMessageConverter init() {
            final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
            ObjectMapper mapper = converter.getObjectMapper();
            // 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
            mapper.getSerializerFactory().withSerializerModifier(new  MyBeanSerializerModifier());
            
    
            converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON));
            return converter;
        }
    }
    

    2.进行spring-mvc.xml的配置

    <context:annotation-config />
        <!-- 激活@Controller模式 -->
        <mvc:annotation-driven >
            <!-- 消息转换器 -->
            <mvc:message-converters register-defaults="true"  >
                <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                    <property name="supportedMediaTypes" value="text/plain;charset=UTF-8" />
                </bean>
                <bean  factory-bean="mappingJackson2HttpMessageConverterFactory" factory-method="init"
                    class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
                </bean>
            </mvc:message-converters>
        </mvc:annotation-driven>
        
        <bean id="mappingJackson2HttpMessageConverterFactory" class = "com.lclc.core.MappingJackson2HttpMessageConverterFactory" />
        <!-- 对包中的所有类进行扫描,以完成Bean创建和自动依赖注入的功能 需要更改 -->
        <context:component-scan base-package="com.lclc" />
         
           <!--其他配置 -->

              

  • 相关阅读:
    Tengine vs openresty
    知名黑客组织Anonymous(匿名者)的装备库
    25个让Java程序员更高效的Eclipse插件
    php提示Fatal error: Call to undefined function imagecreate()
    【转】【iOS】动态更换App图标
    unity在安卓中横屏闪退
    WWW缓存方式
    if UNITY_EDITOR这个判断常用,还有哪个常用捏?
    Lerp和SmoothDamp比较
    UNITY把3D模型显示在UI层级上的思路
  • 原文地址:https://www.cnblogs.com/lic309/p/5048631.html
Copyright © 2011-2022 走看看