赞
踩
最近项目要实现一种需求,对于后端返回给前端的json格式的一种规范,不允许缺少字段和字段值都为null,所以琢磨了一下如何进行将springboot的Jackson序列化自定义一下,先看看如何实现,再去看源码
第一步:写配置类
@Configuration
public class WebConfiguration extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream().filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.map(c ->(MappingJackson2HttpMessageConverter)c)
.forEach(c->{
ObjectMapper mapper = c.getObjectMapper();
// 为mapper注册一个带有SerializerModifier的Factory,此modifier主要做的事情为:当序列化类型为array,list、set时,当值为空时,序列化成[]
mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
c.setObjectMapper(mapper);
});
}
}
第二步:编写值为null时的自定义序列化
/** * @title: MyBeanSerializerModifier * @Author junyu * 旧巷里有一个穿着白衬衫笑起来如太阳般温暖我的少年。 * 记忆里有一个穿着连衣裙哭起来如孩子般讨人喜的女孩。 * 他说,哪年树弯了腰,人见了老,桃花落了白发梢,他讲的笑话她还会笑,那便是好。 * 她说,哪年国改了号,坟长了草,地府过了奈何桥,她回头看时他还在瞧,就不算糟。 * @Date: 2020/9/12 16:44 * @Version 1.0 */ public class MyBeanSerializerModifier extends BeanSerializerModifier { private MyNullStringJsonSerializer myNullStringJsonSerializer; private MyNullArrayJsonSerializer MyNullArrayJsonSerializer; private MyNullObjectJsonSerializer MyNullObjectJsonSerializer; private MyNullJsonSerializer myNullJsonSerializer; public MyBeanSerializerModifier(){ myNullStringJsonSerializer = new MyNullStringJsonSerializer(); MyNullArrayJsonSerializer = new MyNullArrayJsonSerializer(); MyNullObjectJsonSerializer = new MyNullObjectJsonSerializer(); myNullJsonSerializer = new MyNullJsonSerializer(); } @Override public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) { // 循环所有的beanPropertyWriter beanProperties.forEach(writer ->{ // 判断字段的类型 if (isArrayType(writer)) { //给writer注册一个自己的nullSerializer writer.assignNullSerializer(MyNullArrayJsonSerializer); } else if (isObjectType(writer)) { writer.assignNullSerializer(MyNullObjectJsonSerializer); } else if (isStringType(writer)) { writer.assignNullSerializer(myNullStringJsonSerializer); } else if (isPrimitiveType(writer)) { writer.assignNullSerializer(myNullJsonSerializer); } }); return beanProperties; } // 判断是否是boolean类型 private boolean isPrimitiveType(BeanPropertyWriter writer) { Class<?> clazz = writer.getType().getRawClass(); return clazz.isPrimitive(); } // 判断是否是string类型 private boolean isStringType(BeanPropertyWriter writer) { Class<?> clazz = writer.getType().getRawClass(); return clazz.equals(String.class); } // 判断是否是对象类型 private boolean isObjectType(BeanPropertyWriter writer) { Class<?> clazz = writer.getType().getRawClass(); return !clazz.isPrimitive() && !clazz.equals(String.class) && clazz.isAssignableFrom(Object.class); } // 判断是否是集合类型 protected boolean isArrayType(BeanPropertyWriter writer) { Class<?> clazz = writer.getType().getRawClass(); return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class); } class MyNullJsonSerializer extends JsonSerializer<Object>{ @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { if (value == null) { jgen.writeNull(); } } } class MyNullStringJsonSerializer extends JsonSerializer<Object>{ @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { if (value == null) { jgen.writeString(StringUtils.EMPTY); } } } class MyNullArrayJsonSerializer extends JsonSerializer<Object>{ @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { if (value == null) { jgen.writeStartArray(); jgen.writeEndArray(); } } } class MyNullObjectJsonSerializer extends JsonSerializer<Object>{ @Override public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException { if (value == null) { jgen.writeStartObject(); jgen.writeEndObject(); } } } }
这样基本配置就完事了,现在可以试试效果了,自己定义一个bean用来返回,定义一个简单的controller去接受访问就行了,博主就不进行写这两个类了。返回结果如下
这是我的项目需求需要实现的,大家可以根据的自己的需求去改写MyBeanSerializerModifier这个类。还有另一种实现方式:不继承
@Configuration
public class WebConfiguration {
@Bean
public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();
mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
mappingJackson2HttpMessageConverter.setObjectMapper(mapper);
return mappingJackson2HttpMessageConverter;
}
}
这种方法也是可以设置成功的,主要是不是继承了WebMvcConfigurationSupport类,毕竟这个类有很多可以自定义的方法,用起来顺手而已。
第一个问题:为什么继承WebMvcConfigurationSupport后,要重写extendMessageConverters方法;
第二个问题:为什么继承WebMvcConfigurationSupport后,再去生成@Bean的MappingJackson2HttpMessageConverter,却不生效;
第三个问题:为什么不继承WebMvcConfigurationSupport时,生成@Bean的MappingJackson2HttpMessageConverter是生效的;
这几个问题,都需要我们进入源码观察,废活不多说,我们来进入源码的世界。解决问题之前必须搞清楚在哪里进行了序列化。
第一步:我们要弄清楚在哪里进行的Jackson序列化,看这里https://www.processon.com/embed/5f5c6464f346fb7afd55448b,从返回请求开始的序列化基本流程就在这里了,虽然图有点low,但是清楚的记录的每一步,我们主要看一下下面的源码
/* /********************************************************** /* Field serialization methods /********************************************************** */ //序列化每一个字段 protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider) throws IOException { 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 //关键就在这一步进行的序列化,而为什么BeanPropertyWriter是数组,我们一会解释 prop.serializeAsField(bean, gen, provider); } } if (_anyGetterWriter != null) { _anyGetterWriter.getAndSerialize(bean, gen, 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 don't have many // stack frames to spare... just one or two; can't make many calls. // 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly: //JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e); JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e); String name = (i == props.length) ? "[anySetter]" : props[i].getName(); mapE.prependPath(new JsonMappingException.Reference(bean, name)); throw mapE; } }
既然已经找到了在哪里要进行序列化,那我们看看是如何实现的:
/** * Method called to access property that this bean stands for, from within * given bean, and to serialize it as a JSON Object field using appropriate * serializer. */ @Override public void serializeAsField(Object bean, JsonGenerator gen, SerializerProvider prov) throws Exception { // inlined 'get()' final Object value = (_accessorMethod == null) ? _field.get(bean) : _accessorMethod.invoke(bean, (Object[]) null); // Null handling is bit different, check that first if (value == null) { //看到这里大家应该就知道null值是如何进行序列化 的了,如果不配置的话,默认是返回null //因为_nullSerializer是有默认值的,大家看一看这个类的初始化 //那我们要是改一下_nullSerializer的这个默认类,让每一个字段调用我们自己的_nullSerializer不就可以了吗, //yes、我们就这么干 if (_nullSerializer != null) { gen.writeFieldName(_name); _nullSerializer.serialize(null, gen, prov); } return; } // then find serializer to use JsonSerializer<Object> ser = _serializer; if (ser == null) { Class<?> cls = value.getClass(); PropertySerializerMap m = _dynamicSerializers; ser = m.serializerFor(cls); if (ser == null) { ser = _findAndAddDynamic(m, cls, prov); } } // and then see if we must suppress certain values (default, empty) if (_suppressableValue != null) { if (MARKER_FOR_EMPTY == _suppressableValue) { if (ser.isEmpty(prov, value)) { return; } } else if (_suppressableValue.equals(value)) { return; } } // For non-nulls: simple check for direct cycles if (value == bean) { // three choices: exception; handled by call; or pass-through if (_handleSelfReference(bean, gen, prov, ser)) { return; } } gen.writeFieldName(_name); if (_typeSerializer == null) { ser.serialize(value, gen, prov); } else { ser.serializeWithType(value, gen, prov, _typeSerializer); } }
那我们来解决第一个问题:为什么继承WebMvcConfigurationSupport后,要重写extendMessageConverters方法?
不知道大家记得不记得我们请求过来的时候,如果我们配置类集成了WebMvcConfigurationSupport类,dispatchservlet处理handle请求的ha,其实就是RequestMappingHandlerAdapter类,这个类是在WebMvcConfigurationSupport配置的,看源码:
@Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter( @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager, @Qualifier("mvcConversionService") FormattingConversionService conversionService, @Qualifier("mvcValidator") Validator validator) { RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter(); adapter.setContentNegotiationManager(contentNegotiationManager); adapter.setMessageConverters(getMessageConverters()); adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator)); adapter.setCustomArgumentResolvers(getArgumentResolvers()); adapter.setCustomReturnValueHandlers(getReturnValueHandlers()); if (jackson2Present) { adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice())); adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice())); } AsyncSupportConfigurer configurer = new AsyncSupportConfigurer(); configureAsyncSupport(configurer); if (configurer.getTaskExecutor() != null) { adapter.setTaskExecutor(configurer.getTaskExecutor()); } if (configurer.getTimeout() != null) { adapter.setAsyncRequestTimeout(configurer.getTimeout()); } adapter.setCallableInterceptors(configurer.getCallableInterceptors()); adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors()); return adapter; }
adapter.setMessageConverters(getMessageConverters());当大家看到这个方法的时候,应该就会想到我们的默认jackson转换器:MappingJackson2HttpMessageConverter,我们看看这个getMessageConverters()有什么幺蛾子:
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
1 protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { 2 3 //这些都不用管,跟我们的需求没啥作用,我们只看关键的部分,在下面 4 messageConverters.add(new ByteArrayHttpMessageConverter()); 5 messageConverters.add(new StringHttpMessageConverter()); 6 messageConverters.add(new ResourceHttpMessageConverter()); 7 messageConverters.add(new ResourceRegionHttpMessageConverter()); 8 try { 9 messageConverters.add(new SourceHttpMessageConverter<>()); 10 } 11 catch (Throwable ex) { 12 // Ignore when no TransformerFactory implementation is available... 13 } 14 messageConverters.add(new AllEncompassingFormHttpMessageConverter()); 15 16 if (romePresent) { 17 messageConverters.add(new AtomFeedHttpMessageConverter()); 18 messageConverters.add(new RssChannelHttpMessageConverter()); 19 } 20 21 if (jackson2XmlPresent) { 22 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml(); 23 if (this.applicationContext != null) { 24 builder.applicationContext(this.applicationContext); 25 } 26 messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build())); 27 } 28 else if (jaxb2Present) { 29 messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); 30 } 31 32 if (jackson2Present) { 33 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); 34 if (this.applicationContext != null) { 35 builder.applicationContext(this.applicationContext); 36 } 37 //解析我们返回值的转换器就是在这里生成的 38 messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); 39 } 40 else if (gsonPresent) { 41 messageConverters.add(new GsonHttpMessageConverter()); 42 } 43 else if (jsonbPresent) { 44 messageConverters.add(new JsonbHttpMessageConverter()); 45 } 46 47 if (jackson2SmilePresent) { 48 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile(); 49 if (this.applicationContext != null) { 50 builder.applicationContext(this.applicationContext); 51 } 52 messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build())); 53 } 54 if (jackson2CborPresent) { 55 Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor(); 56 if (this.applicationContext != null) { 57 builder.applicationContext(this.applicationContext); 58 } 59 messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build())); 60 } 61 }
我们的MappingJackson2HttpMessageConverter类就是这里初始化的,初始化的时候默认的_nullSerializer也会被初始化,大家肯定说这已经初始化完了,该咋办,大家应该看到了extendMessageConverters(this.messageConverters);这个方法就是用来重写实现的了,这回知道我们继承WebMvcConfigurationSupport后,为什么要重写extendMessageConverters,我们的配置类遍历已经获取到的convert,然后对我们想要的转换器进行修改添加,那修改完了,是在哪里起作用的呢,我们再来看一看源码:
在序列化之前有一些方法是可以进行修改操作的,在调用writeWithMessageConverters方法的时候:
1 protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType, 2 ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage) 3 throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { 4 5 ....... 6 7 MediaType selectedMediaType = null; 8 MediaType contentType = outputMessage.getHeaders().getContentType(); 9 boolean isContentTypePreset = contentType != null && contentType.isConcrete(); 10 if (isContentTypePreset) { 11 if (logger.isDebugEnabled()) { 12 logger.debug("Found 'Content-Type:" + contentType + "' in response"); 13 } 14 selectedMediaType = contentType; 15 } 16 else { 17 HttpServletRequest request = inputMessage.getServletRequest(); 18 List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); 19 //这里进行自定义操作修改MappingJackson2HttpMessageConverter 20 List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType); 21 22 ....... 23 24 if (selectedMediaType != null) { 25 selectedMediaType = selectedMediaType.removeQualityValue(); 26 //这这里进行选择我们的MappingJackson2HttpMessageConverter去自定义序列化 27 for (HttpMessageConverter<?> converter : this.messageConverters) { 28 GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? 29 (GenericHttpMessageConverter<?>) converter : null); 30 if (genericConverter != null ? 31 ((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) : 32 converter.canWrite(valueType, selectedMediaType)) { 33 body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType, 34 (Class<? extends HttpMessageConverter<?>>) converter.getClass(), 35 inputMessage, outputMessage); 36 if (body != null) { 37 Object theBody = body; 38 LogFormatUtils.traceDebug(logger, traceOn -> 39 "Writing [" + LogFormatUtils.formatValue(theBody, !traceOn) + "]"); 40 addContentDispositionHeader(inputMessage, outputMessage); 41 if (genericConverter != null) { 42 genericConverter.write(body, targetType, selectedMediaType, outputMessage); 43 } 44 else { 45 ((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage); 46 } 47 } 48 else { 49 if (logger.isDebugEnabled()) { 50 logger.debug("Nothing to write: null body"); 51 } 52 } 53 return; 54 } 55 } 56 } 57 58 if (body != null) { 59 Set<MediaType> producibleMediaTypes = 60 (Set<MediaType>) inputMessage.getServletRequest() 61 .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); 62 63 if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) { 64 throw new HttpMessageNotWritableException( 65 "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'"); 66 } 67 throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); 68 } 69 }
[](javascript:void(0)声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/思考机器5/article/detail/61434
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。