原始碼分析springboot自定義jackson序列化,預設null值個性化處理返回值

努力的小雨發表於2020-09-25

  最近專案要實現一種需求,對於後端返回給前端的json格式的一種規範,不允許缺少欄位和欄位值都為null,所以琢磨了一下如何進行將springboot的Jackson序列化自定義一下,先看看如何實現,再去看原始碼

第一步:寫配置類

 1 @Configuration
 2 public class WebConfiguration  extends WebMvcConfigurationSupport {
 3 @Override
 4     protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
 5         converters.stream().filter(c -> c instanceof MappingJackson2HttpMessageConverter)
 6                 .map(c ->(MappingJackson2HttpMessageConverter)c)
 7                 .forEach(c->{
 8                     ObjectMapper mapper = c.getObjectMapper();
 9                     // 為mapper註冊一個帶有SerializerModifier的Factory,此modifier主要做的事情為:當序列化型別為array,list、set時,當值為空時,序列化成[]
10                     mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
11                     c.setObjectMapper(mapper);
12                 });
13     }
14 }

第二步:編寫值為null時的自定義序列化

  1 /**
  2  * @title: MyBeanSerializerModifier
  3  * @Author junyu
  4  * 舊巷裡有一個穿著白襯衫笑起來如太陽般溫暖我的少年。
  5  * 記憶裡有一個穿著連衣裙哭起來如孩子般討人喜的女孩。
  6  * 他說,哪年樹彎了腰,人見了老,桃花落了白髮梢,他講的笑話她還會笑,那便是好。
  7  * 她說,哪年國改了號,墳長了草,地府過了奈何橋,她回頭看時他還在瞧,就不算糟。
  8  * @Date: 2020/9/12 16:44
  9  * @Version 1.0
 10  */
 11 public class MyBeanSerializerModifier extends BeanSerializerModifier {
 12 
 13     private MyNullStringJsonSerializer myNullStringJsonSerializer;
 14     private MyNullArrayJsonSerializer MyNullArrayJsonSerializer;
 15     private MyNullObjectJsonSerializer MyNullObjectJsonSerializer;
 16     private MyNullJsonSerializer myNullJsonSerializer;
 17 
 18     public MyBeanSerializerModifier(){
 19         myNullStringJsonSerializer = new MyNullStringJsonSerializer();
 20         MyNullArrayJsonSerializer = new MyNullArrayJsonSerializer();
 21         MyNullObjectJsonSerializer =  new MyNullObjectJsonSerializer();
 22         myNullJsonSerializer = new MyNullJsonSerializer();
 23     }
 24 
 25     @Override
 26     public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc,
 27                                                      List<BeanPropertyWriter> beanProperties) {
 28         // 迴圈所有的beanPropertyWriter
 29         beanProperties.forEach(writer ->{
 30             // 判斷欄位的型別
 31             if (isArrayType(writer)) {
 32                 //給writer註冊一個自己的nullSerializer
 33                 writer.assignNullSerializer(MyNullArrayJsonSerializer);
 34             } else if (isObjectType(writer)) {
 35                 writer.assignNullSerializer(MyNullObjectJsonSerializer);
 36             } else if (isStringType(writer)) {
 37                 writer.assignNullSerializer(myNullStringJsonSerializer);
 38             } else if (isPrimitiveType(writer)) {
 39                 writer.assignNullSerializer(myNullJsonSerializer);
 40             }
 41         });
 42         return beanProperties;
 43     }
 44 
 45     // 判斷是否是boolean型別
 46     private boolean isPrimitiveType(BeanPropertyWriter writer) {
 47         Class<?> clazz = writer.getType().getRawClass();
 48         return clazz.isPrimitive();
 49     }
 50 
 51     // 判斷是否是string型別
 52     private boolean isStringType(BeanPropertyWriter writer) {
 53         Class<?> clazz = writer.getType().getRawClass();
 54         return clazz.equals(String.class);
 55     }
 56 
 57     // 判斷是否是物件型別
 58     private boolean isObjectType(BeanPropertyWriter writer) {
 59         Class<?> clazz = writer.getType().getRawClass();
 60         return !clazz.isPrimitive() && !clazz.equals(String.class)
 61                 && clazz.isAssignableFrom(Object.class);
 62     }
 63     // 判斷是否是集合型別
 64     protected boolean isArrayType(BeanPropertyWriter writer) {
 65         Class<?> clazz = writer.getType().getRawClass();
 66         return clazz.isArray() || clazz.equals(List.class) || clazz.equals(Set.class);
 67     }
 68 
 69     class MyNullJsonSerializer extends JsonSerializer<Object>{
 70 
 71         @Override
 72         public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
 73             if (value == null) {
 74                 jgen.writeNull();
 75             }
 76         }
 77     }
 78 
 79 
 80     class MyNullStringJsonSerializer extends JsonSerializer<Object>{
 81 
 82         @Override
 83         public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
 84             if (value == null) {
 85                 jgen.writeString(StringUtils.EMPTY);
 86             }
 87         }
 88     }
 89 
 90     class MyNullArrayJsonSerializer extends JsonSerializer<Object>{
 91 
 92         @Override
 93         public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
 94             if (value == null) {
 95                 jgen.writeStartArray();
 96                 jgen.writeEndArray();
 97             }
 98         }
 99     }
100 
101     class MyNullObjectJsonSerializer extends JsonSerializer<Object>{
102 
103         @Override
104         public void serialize(Object value, JsonGenerator jgen, SerializerProvider serializers) throws IOException {
105             if (value == null) {
106                 jgen.writeStartObject();
107                 jgen.writeEndObject();
108             }
109         }
110     }
111 
112 }

  這樣基本配置就完事了,現在可以試試效果了,自己定義一個bean用來返回,定義一個簡單的controller去接受訪問就行了,博主就不進行寫這兩個類了。返回結果如下

   這是我的專案需求需要實現的,大家可以根據的自己的需求去改寫MyBeanSerializerModifier這個類。還有另一種實現方式:不繼承

 

 1 @Configuration
 2 public class WebConfiguration {
 3 
 4 @Bean
 5     public MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter(){
 6         MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
 7         ObjectMapper mapper = mappingJackson2HttpMessageConverter.getObjectMapper();
 8         mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new MyBeanSerializerModifier()));
 9         mappingJackson2HttpMessageConverter.setObjectMapper(mapper);
10         return mappingJackson2HttpMessageConverter;
11     }
12 
13 }

  這種方法也是可以設定成功的,主要是不是繼承了WebMvcConfigurationSupport類,畢竟這個類有很多可以自定義的方法,用起來順手而已。

  第一個問題:為什麼繼承WebMvcConfigurationSupport後,要重寫extendMessageConverters方法;

  第二個問題:為什麼繼承WebMvcConfigurationSupport後,再去生成@Bean的MappingJackson2HttpMessageConverter,卻不生效;

  第三個問題:為什麼不繼承WebMvcConfigurationSupport時,生成@Bean的MappingJackson2HttpMessageConverter是生效的;

  這幾個問題,都需要我們進入原始碼觀察,廢活不多說,我們來進入原始碼的世界。解決問題之前必須搞清楚在哪裡進行了序列化。

  第一步:我們要弄清楚在哪裡進行的Jackson序列化,看這裡https://www.processon.com/embed/5f5c6464f346fb7afd55448b,從返回請求開始的序列化基本流程就在這裡了,雖然圖有點low,但是清楚的記錄的每一步,我們主要看一下下面的原始碼

 1 /*
 2     /**********************************************************
 3     /* Field serialization methods
 4     /**********************************************************
 5      */
 6     //序列化每一個欄位
 7     protected void serializeFields(Object bean, JsonGenerator gen, SerializerProvider provider)
 8             throws IOException
 9     {
10         final BeanPropertyWriter[] props;
11         if (_filteredProps != null && provider.getActiveView() != null) {
12             props = _filteredProps;
13         } else {
14             props = _props;
15         }
16         int i = 0;
17         try {
18             for (final int len = props.length; i < len; ++i) {
19                 BeanPropertyWriter prop = props[i];
20                 if (prop != null) { // can have nulls in filtered list
21                     //關鍵就在這一步進行的序列化,而為什麼BeanPropertyWriter是陣列,我們一會解釋
22                     prop.serializeAsField(bean, gen, provider);
23                 }
24             }
25             if (_anyGetterWriter != null) {
26                 _anyGetterWriter.getAndSerialize(bean, gen, provider);
27             }
28         } catch (Exception e) {
29             String name = (i == props.length) ? "[anySetter]" : props[i].getName();
30             wrapAndThrow(provider, e, bean, name);
31         } catch (StackOverflowError e) {
32             // 04-Sep-2009, tatu: Dealing with this is tricky, since we don't have many
33             //   stack frames to spare... just one or two; can't make many calls.
34 
35             // 10-Dec-2015, tatu: and due to above, avoid "from" method, call ctor directly:
36             //JsonMappingException mapE = JsonMappingException.from(gen, "Infinite recursion (StackOverflowError)", e);
37             JsonMappingException mapE = new JsonMappingException(gen, "Infinite recursion (StackOverflowError)", e);
38 
39             String name = (i == props.length) ? "[anySetter]" : props[i].getName();
40             mapE.prependPath(new JsonMappingException.Reference(bean, name));
41             throw mapE;
42         }
43     }

  既然已經找到了在哪裡要進行序列化,那我們看看是如何實現的:

 1 /**
 2      * Method called to access property that this bean stands for, from within
 3      * given bean, and to serialize it as a JSON Object field using appropriate
 4      * serializer.
 5      */
 6     @Override
 7     public void serializeAsField(Object bean, JsonGenerator gen,
 8                                  SerializerProvider prov) throws Exception {
 9         // inlined 'get()'
10         final Object value = (_accessorMethod == null) ? _field.get(bean)
11                 : _accessorMethod.invoke(bean, (Object[]) null);
12 
13         // Null handling is bit different, check that first
14         if (value == null) {
15             //看到這裡大家應該就知道null值是如何進行序列化 的了,如果不配置的話,預設是返回null
16             //因為_nullSerializer是有預設值的,大家看一看這個類的初始化
17             //那我們要是改一下_nullSerializer的這個預設類,讓每一個欄位呼叫我們自己的_nullSerializer不就可以了嗎,
18             //yes、我們就這麼幹
19             if (_nullSerializer != null) {
20                 gen.writeFieldName(_name);
21                 _nullSerializer.serialize(null, gen, prov);
22             }
23             return;
24         }
25         // then find serializer to use
26         JsonSerializer<Object> ser = _serializer;
27         if (ser == null) {
28             Class<?> cls = value.getClass();
29             PropertySerializerMap m = _dynamicSerializers;
30             ser = m.serializerFor(cls);
31             if (ser == null) {
32                 ser = _findAndAddDynamic(m, cls, prov);
33             }
34         }
35         // and then see if we must suppress certain values (default, empty)
36         if (_suppressableValue != null) {
37             if (MARKER_FOR_EMPTY == _suppressableValue) {
38                 if (ser.isEmpty(prov, value)) {
39                     return;
40                 }
41             } else if (_suppressableValue.equals(value)) {
42                 return;
43             }
44         }
45         // For non-nulls: simple check for direct cycles
46         if (value == bean) {
47             // three choices: exception; handled by call; or pass-through
48             if (_handleSelfReference(bean, gen, prov, ser)) {
49                 return;
50             }
51         }
52         gen.writeFieldName(_name);
53         if (_typeSerializer == null) {
54             ser.serialize(value, gen, prov);
55         } else {
56             ser.serializeWithType(value, gen, prov, _typeSerializer);
57         }
58     }

  那我們來解決第一個問題:為什麼繼承WebMvcConfigurationSupport後,要重寫extendMessageConverters方法?

  不知道大家記得不記得我們請求過來的時候,如果我們配置類整合了WebMvcConfigurationSupport類,dispatchservlet處理handle請求的ha,其實就是RequestMappingHandlerAdapter類,這個類是在WebMvcConfigurationSupport配置的,看原始碼:

 1 @Bean
 2     public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
 3             @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
 4             @Qualifier("mvcConversionService") FormattingConversionService conversionService,
 5             @Qualifier("mvcValidator") Validator validator) {
 6 
 7         RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
 8         adapter.setContentNegotiationManager(contentNegotiationManager);
 9         adapter.setMessageConverters(getMessageConverters());
10         adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
11         adapter.setCustomArgumentResolvers(getArgumentResolvers());
12         adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
13 
14         if (jackson2Present) {
15             adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
16             adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
17         }
18 
19         AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
20         configureAsyncSupport(configurer);
21         if (configurer.getTaskExecutor() != null) {
22             adapter.setTaskExecutor(configurer.getTaskExecutor());
23         }
24         if (configurer.getTimeout() != null) {
25             adapter.setAsyncRequestTimeout(configurer.getTimeout());
26         }
27         adapter.setCallableInterceptors(configurer.getCallableInterceptors());
28         adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
29 
30         return adapter;
31     }

  adapter.setMessageConverters(getMessageConverters());當大家看到這個方法的時候,應該就會想到我們的預設jackson轉換器:MappingJackson2HttpMessageConverter,我們看看這個getMessageConverters()有什麼么蛾子:

 1 protected final List<HttpMessageConverter<?>> getMessageConverters() {
 2         if (this.messageConverters == null) {
 3             this.messageConverters = new ArrayList<>();
 4             configureMessageConverters(this.messageConverters);
 5             if (this.messageConverters.isEmpty()) {
 6                 addDefaultHttpMessageConverters(this.messageConverters);
 7             }
 8             extendMessageConverters(this.messageConverters);
 9         }
10         return this.messageConverters;
11     } 
原始碼分析springboot自定義jackson序列化,預設null值個性化處理返回值
 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         }
addDefaultHttpMessageConverters

  我們的MappingJackson2HttpMessageConverter類就是這裡初始化的,初始化的時候預設的_nullSerializer也會被初始化,大家肯定說這已經初始化完了,該咋辦,大家應該看到了extendMessageConverters(this.messageConverters);這個方法就是用來重寫實現的了,這回知道我們繼承WebMvcConfigurationSupport後,為什麼要重寫extendMessageConverters,我們的配置類遍歷已經獲取到的convert,然後對我們想要的轉換器進行修改新增,那修改完了,是在哪裡起作用的呢,我們再來看一看原始碼:

在序列化之前有一些方法是可以進行修改操作的,在呼叫writeWithMessageConverters方法的時候:

原始碼分析springboot自定義jackson序列化,預設null值個性化處理返回值
 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         }
writeWithMessageConverters

  我們一直追蹤getProducibleMediaTypes後,最終發現會呼叫BeanSerializerFactory的constructBeanOrAddOnSerializer,就是這裡進行修改操作的。

 1 protected JsonSerializer<Object> constructBeanOrAddOnSerializer(SerializerProvider prov,
 2             JavaType type, BeanDescription beanDesc, boolean staticTyping)
 3         throws JsonMappingException
 4     {
 5         // 13-Oct-2010, tatu: quick sanity check: never try to create bean serializer for plain Object
 6         // 05-Jul-2012, tatu: ... but we should be able to just return "unknown type" serializer, right?
 7         if (beanDesc.getBeanClass() == Object.class) {
 8             return prov.getUnknownTypeSerializer(Object.class);
 9 //            throw new IllegalArgumentException("Cannot create bean serializer for Object.class");
10         }
11         final SerializationConfig config = prov.getConfig();
12         BeanSerializerBuilder builder = constructBeanSerializerBuilder(beanDesc);
13         builder.setConfig(config);
14 
15         // First: any detectable (auto-detect, annotations) properties to serialize?
16         List<BeanPropertyWriter> props = findBeanProperties(prov, beanDesc, builder);
17         if (props == null) {
18             props = new ArrayList<BeanPropertyWriter>();
19         } else {
20             props = removeOverlappingTypeIds(prov, beanDesc, builder, props);
21         }
22         
23         // [databind#638]: Allow injection of "virtual" properties:
24         prov.getAnnotationIntrospector().findAndAddVirtualProperties(config, beanDesc.getClassInfo(), props);
25 
26         // [JACKSON-440] Need to allow modification bean properties to serialize:
27         if (_factoryConfig.hasSerializerModifiers()) {
28             for (BeanSerializerModifier mod : _factoryConfig.serializerModifiers()) {
29                 props = mod.changeProperties(config, beanDesc, props);
30             }
31         }
32 .......//此處省略
33 }

  大家看一下props = mod.changeProperties(config, beanDesc, props);我們在配置類裡面可是為我們的MappingJackson2HttpMessageConverter配置了 withSerializerModifier方法,並且設定了我們的MyBeanSerializerModifier並且繼承BeanSerializerModifier重寫了 changeProperties,所以會呼叫我們的changeProperties方法,進行修改null值的序列化類,我們也返回了一個list型別的BeanPropertyWriter,所以知道為什麼那個BeanPropertyWriter在解析時,會是個陣列型別的了吧,因為不同欄位解析是不一樣的。

剩下的解釋一下為什麼單獨配置並設定例項化@bean的MappingJackson2HttpMessageConverter也是好使的呢,大家可以看一下JacksonHttpMessageConvertersConfiguration類的原始碼,裡面明確寫了@ConditionalOnMissingBean註解,如果我們自己進行配置了,這個注入後就是一個備胎,以我們的為準,這個不多說

  我們再來解析一下第二個問題:為什麼繼承WebMvcConfigurationSupport後,再去生成@Bean的MappingJackson2HttpMessageConverter,卻不生效,這需要跟第三個問題一起解決:為什麼不繼承WebMvcConfigurationSupport時,生成@Bean的MappingJackson2HttpMessageConverter是生效的。

  我們知道當我們繼承WebMvcConfigurationSupport後,有一個配置會自動失效,就是自動注入的一個mvc配置,可以看看@SpringBootApplication註解裡面有個@EnableAutoConfiguration註解,會引入一個AutoConfigurationImportSelector類。這個類就會掃描org.springframework.boot:spring-boot-autoconfigure下的spring.factories檔案,這裡面有一個我們預設的mvn配置也是繼承了WebMvcConfigurationSupport,叫WebMvcAutoConfiguration,我們來看一下原始碼:

 

 1 @Configuration(proxyBeanMethods = false)
 2 @ConditionalOnWebApplication(type = Type.SERVLET)
 3 @ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
 4 //注意此處有一個ConditionalOnMissingBean註解,所以如果我們自己繼承後,就相當於已經存在WebMvcConfigurationSupport類,
 5 //就會走我們自己的配置類,此配置會失效
 6 @ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
 7 @AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
 8 @AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
 9         ValidationAutoConfiguration.class })
10 public class WebMvcAutoConfiguration {
11     .....
12 
13     @Configuration(proxyBeanMethods = false)
14     public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware {
15 
16         private final ResourceProperties resourceProperties;
17 
18         private final WebMvcProperties mvcProperties;
19 
20         private final ListableBeanFactory beanFactory;
21 
22         private final WebMvcRegistrations mvcRegistrations;
23 
24         private ResourceLoader resourceLoader;
25 
26         public EnableWebMvcConfiguration(ResourceProperties resourceProperties,
27                 ObjectProvider<WebMvcProperties> mvcPropertiesProvider,
28                 ObjectProvider<WebMvcRegistrations> mvcRegistrationsProvider, ListableBeanFactory beanFactory) {
29             this.resourceProperties = resourceProperties;
30             this.mvcProperties = mvcPropertiesProvider.getIfAvailable();
31             this.mvcRegistrations = mvcRegistrationsProvider.getIfUnique();
32             this.beanFactory = beanFactory;
33         }
34         //如果我們不繼承的話,處理請求的RequestMappingHandlerAdapter就會在這裡生成
35         //會呼叫DelegatingWebMvcConfiguration裡面的 requestMappingHandlerAdapter方法,
36         @Bean
37         @Override
38         public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
39                 @Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
40                 @Qualifier("mvcConversionService") FormattingConversionService conversionService,
41                 @Qualifier("mvcValidator") Validator validator) {
42             RequestMappingHandlerAdapter adapter = super.requestMappingHandlerAdapter(contentNegotiationManager,
43                     conversionService, validator);
44             adapter.setIgnoreDefaultModelOnRedirect(
45                     this.mvcProperties == null || this.mvcProperties.isIgnoreDefaultModelOnRedirect());
46             return adapter;
47         }
48 
49 .....
50 }

 

  不知道大家是否還記得getMessageConverters()方法裡面新增預設messageConverters的時候之前,會呼叫一個configureMessageConverters(this.messageConverters);方法,我們的DelegatingWebMvcConfiguration 就已經重寫了這個方法,所以我們如果不繼承WebMvcConfigurationSupport,將會把我們的@bean形式存在的MappingJackson2HttpMessageConverter將會被掃描到

 

 1 @Override
 2     protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
 3         this.configurers.configureMessageConverters(converters);
 4     }
 5 
 6 //會新增我們的convert
 7 @Override
 8     public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
 9         for (WebMvcConfigurer delegate : this.delegates) {
10             delegate.configureMessageConverters(converters);
11         }
12     }

  現在我們配置的自定義jackson序列化已經生效了,但是,你仔細看我的流程圖會發現,其實呼叫序列化的時候走的是RequestResponseBodyMethodProcessor的handleReturnValue方法

 

 1 @Override
 2     public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
 3             ModelAndViewContainer mavContainer, NativeWebRequest webRequest)
 4             throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
 5 
 6         mavContainer.setRequestHandled(true);
 7         ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
 8         ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
 9 
10         // Try even with null return value. ResponseBodyAdvice could get involved.
11         //這裡進入序列化流程
12         writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
13     }

 

  最後在序列化的時候也是從這個類或則父類裡面的一個屬性:messageConverters

 

public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {

    private static final Set<HttpMethod> SUPPORTED_METHODS =
            EnumSet.of(HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH);

    private static final Object NO_VALUE = new Object();


    protected final Log logger = LogFactory.getLog(getClass());
    //這個屬性取值的
    protected final List<HttpMessageConverter<?>> messageConverters;

    protected final List<MediaType> allSupportedMediaTypes;

    private final RequestResponseBodyAdviceChain advice;

...
}

  於是,小夥伴們就疑惑了,這我們自定義的在RequestMappingHandlerAdapter裡面呢,跟這個類也沒關係啊,屬性是咋設定進來的呢?我們再看看RequestMappingHandlerAdapter的原始碼,你會發現,RequestMappingHandlerAdapter這個類實現了InitializingBean類,也就說明,建立RequestMappingHandlerAdapter的時候會呼叫afterPropertiesSet方法,至於為啥,看原始碼吧:(不是主要流程)

原始碼分析springboot自定義jackson序列化,預設null值個性化處理返回值
 1 //在createBean的時候會呼叫這個方法,看看是否實現了InitializingBean
 2 protected void invokeInitMethods(String beanName, final Object bean, @Nullable RootBeanDefinition mbd)
 3             throws Throwable {
 4 
 5         boolean isInitializingBean = (bean instanceof InitializingBean);
 6         if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) {
 7             if (logger.isTraceEnabled()) {
 8                 logger.trace("Invoking afterPropertiesSet() on bean with name '" + beanName + "'");
 9             }
10             if (System.getSecurityManager() != null) {
11                 try {
12                     AccessController.doPrivileged((PrivilegedExceptionAction<Object>) () -> {
13                         ((InitializingBean) bean).afterPropertiesSet();
14                         return null;
15                     }, getAccessControlContext());
16                 }
17                 catch (PrivilegedActionException pae) {
18                     throw pae.getException();
19                 }
20             }
21             else {
22                 //在這裡進行呼叫的,
23                 ((InitializingBean) bean).afterPropertiesSet();
24             }
25         }
26 
27         if (mbd != null && bean.getClass() != NullBean.class) {
28             String initMethodName = mbd.getInitMethodName();
29             if (StringUtils.hasLength(initMethodName) &&
30                     !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) &&
31                     !mbd.isExternallyManagedInitMethod(initMethodName)) {
32                 invokeCustomInitMethod(beanName, bean, mbd);
33             }
34         }
35     }
invokeInitMethods

  那我們看看RequestMappingHandlerAdapter的afterPropertiesSet方法都幹了些啥吧。

 

 1 @Override
 2     public void afterPropertiesSet() {
 3         // Do this first, it may add ResponseBody advice beans
 4         initControllerAdviceCache();
 5 
 6         if (this.argumentResolvers == null) {
 7             List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
 8             this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
 9         }
10         if (this.initBinderArgumentResolvers == null) {
11             List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
12             this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
13         }
14         if (this.returnValueHandlers == null) {
15             //是在這裡生成的類
16             List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
17             this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
18         }
19     }

 

private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
        List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>();

        // Single-purpose return value types
        handlers.add(new ModelAndViewMethodReturnValueHandler());
        handlers.add(new ModelMethodProcessor());
        handlers.add(new ViewMethodReturnValueHandler());
        handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
                this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
        handlers.add(new StreamingResponseBodyReturnValueHandler());
        handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));
        handlers.add(new HttpHeadersReturnValueHandler());
        handlers.add(new CallableMethodReturnValueHandler());
        handlers.add(new DeferredResultMethodReturnValueHandler());
        handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));

        // Annotation-based return value types
        handlers.add(new ModelAttributeMethodProcessor(false));
        //看到這個類了嗎?生成的時候將RequestMappingHandlerAdapter裡面的轉換器設定進去了
        handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
                this.contentNegotiationManager, this.requestResponseBodyAdvice));

        // Multi-purpose return value types
        handlers.add(new ViewNameMethodReturnValueHandler());
        handlers.add(new MapMethodProcessor());

        // Custom return value types
        if (getCustomReturnValueHandlers() != null) {
            handlers.addAll(getCustomReturnValueHandlers());
        }

        // Catch-all
        if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
            handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
        }
        else {
            handlers.add(new ModelAttributeMethodProcessor(true));
        }

        return handlers;
    }

  講到這裡,不知道大家理解了多少,這些都是博主遇到需求後,自己問自己的問題,自己通過原始碼回答問題的,也希望大家能理解原始碼。還有一篇原始碼文章在路上:為什麼我們的專案裡出現兩個配置類繼承WebMvcConfigurationSupport時,只有一個會生效。我在網上找了半天都是說結果的,沒有人分析原始碼到底是為啥,博主準備講解一下,希望可以幫到大家!


 

相關文章