最近用springMVC作服務端的http+json的接口,出現一個不是特別容易解決的問題:java
在對List類型的值進行處理時,有一部分服務是有作一些邏輯判斷的,在邏輯判斷不經過的時候會返回一個null值,git
而有一些值是直接經過jpa查詢到的List類型的值則會進行實例化,即一樣是List類型,一個是null,一個"[]"。github
最簡單的辦法是在null值的地方所有實例化一個new ArrayList<?>(0);可是這樣會修改不少地方,並且對於這些狀況都要進行實例化分配內存不是那麼的理想。spring
因此就在springMvc轉json的地方作手腳。json
咱們都知道springMvc是使用jackson作的json序列化工具。 spring-mvc
@Bean public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() { final MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter(); converter.setSupportedMediaTypes(ImmutableList.of(MediaType.TEXT_HTML, MediaType.APPLICATION_JSON)); return converter; }
能夠配置其一個MappingJackson2HttpMessageConverter類,這個類同時能夠作另外一個事情,防止ie對json數據當作文件進行下載。mvc
MappingJackson2HttpMessageConverter類中能夠取到一個ObjectMapper,即jackson序列化的主類。app
查看代碼看到:ide
@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); } }
看到使用了spring-boot
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" /> <!--其餘配置 -->