最近用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" /> <!--其他配置 -->