最近專案要實現一種需求,對於後端返回給前端的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 }
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 }
我們一直追蹤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方法,至於為啥,看原始碼吧:(不是主要流程)
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 }
那我們看看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時,只有一個會生效。我在網上找了半天都是說結果的,沒有人分析原始碼到底是為啥,博主準備講解一下,希望可以幫到大家!