extends WebMvcConfigurationSupport 和 implements WebMvcConfigurer之间的区别
事情是这样的,springboot项目中原有一个类是 implements WebMvcConfigurer这样的
package com.davidhu.shopguide.web.config; //import javax.annotation.Resource; //import com.davidhu.shopguide.web.interceptor.CorsInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.List; /** * @author <a href="hugaoxiang8619@adpanshi.com">hugaoxiang</a> * @version V1.0.1 * @Description * @date 2019年5月9日 下午7:16:15 */ @Configuration @EnableWebMvc public class WebMvcConfig implements WebMvcConfigurer { // @Resource // private CorsInterceptor corsInterceptor; // @Override // public void addInterceptors(InterceptorRegistry registry) { // // registry.addInterceptor(corsInterceptor).addPathPatterns("/**"); // // // } // @Override // public void addResourceHandlers(ResourceHandlerRegistry registry) { // registry.addResourceHandler("/api/doc/**").addResourceLocations("/api/doc/"); // } @Override public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) { // Turn off suffix-based content negotiation configurer.favorPathExtension(false); } @Bean public RequestMappingHandlerAdapter requestMappingHandlerAdapter() { RequestMappingHandlerAdapter adapter = new RequestMappingHandlerAdapter(); List<HttpMessageConverter<?>> converters = adapter.getMessageConverters(); MappingJackson2XmlHttpMessageConverter xmlConverter = new MappingJackson2XmlHttpMessageConverter(); MappingJackson2HttpMessageConverter jsonConverter = new MappingJackson2HttpMessageConverter(); List<MediaType> supportedMediaTypes = new ArrayList<MediaType>(); MediaType jsonMedis = new MediaType(MediaType.APPLICATION_JSON, Charset.forName("UTF-8")); supportedMediaTypes.add(jsonMedis); jsonConverter.setSupportedMediaTypes(supportedMediaTypes); converters.add(jsonConverter); supportedMediaTypes = new ArrayList<MediaType>(); MediaType xmlMedia = new MediaType(MediaType.TEXT_XML, Charset.forName("UTF-8")); supportedMediaTypes.add(xmlMedia); xmlConverter.setSupportedMediaTypes(supportedMediaTypes); converters.add(xmlConverter); adapter.setMessageConverters(converters); return adapter; } @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); Jackson2ObjectMapperFactoryBean factory = new Jackson2ObjectMapperFactoryBean(); factory.setSimpleDateFormat("yyyy-MM-dd HH:mm:ss"); factory.afterPropertiesSet(); MappingJackson2HttpMessageConverter jackson2Converter = new MappingJackson2HttpMessageConverter(); jackson2Converter.setObjectMapper(factory.getObject()); converters.add(jackson2Converter); } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true).allowedHeaders("*"); } }
因为java后端的Long型数据返回前端时,js的数字会丢失精度,于是在configureMessageConverters中加上一个MappingJackson2HttpMessageConverter
@Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { //解决Long型在js里精度丢失 MappingJackson2HttpMessageConverter jackson2Converter = new MappingJackson2HttpMessageConverter(); ObjectMapper objectMapper = new ObjectMapper(); // objectMapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); jackson2Converter.setObjectMapper(objectMapper); converters.add(jackson2Converter); converters.add(new StringHttpMessageConverter(Charset.forName("UTF-8"))); }
但问题总是没有生效,看到网上有人用 extends WebMvcConfigurationSupport ,结果改了之后原先没有的跨域问题却出现了。addCorsMappings不生效了。
后来在github上有人讨论这个问题:https://stackoverflow.com/questions/17898606/difference-between-webmvcconfigurationsupport-and-webmvcconfigureradap
The answer is in the doc you referenced above:
If the customization options of WebMvcConfigurer do not expose something you need to configure, consider removing the @EnableWebMvc annotation and extending directly from WebMvcConfigurationSupport overriding selected @Bean methods
In short, if @EnableWebMvc works for you, there is no need to look any further.
也就是 @EnableWebMvc 会做很多默认设置,要自定义的话需要把@EnableWebMvc去掉然后extends WebMvcConfigurationSupport,我这里虽然改成了WebMvcConfigurationSupport,但没有把 @EnableWebMvc去掉所以跨域不生效了。
但是这样改要注意,有可能会出现之前没有的问题,这次extends WebMvcConfigurationSupport之后发现RequestMappingHandlerAdapter的配置也失效了。之前配置RequestMappingHandlerAdapter是为解决不支持xml/text的请求的。
看WebMvcConfigurationSupport代码,这里已经配置了一个 RequestMappingHandlerAdapter
/**
* Returns a {@link RequestMappingHandlerAdapter} for processing requests
* through annotated controller methods. Consider overriding one of these
* other more fine-grained methods:
* <ul>
* <li>{@link #addArgumentResolvers} for adding custom argument resolvers.
* <li>{@link #addReturnValueHandlers} for adding custom return value handlers.
* <li>{@link #configureMessageConverters} for adding custom message converters.
* </ul>
*/
@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()); 支持的MediaType在MessageConverter里设置。
/** * Provides access to the shared {@link HttpMessageConverter HttpMessageConverters} * used by the {@link RequestMappingHandlerAdapter} and the * {@link ExceptionHandlerExceptionResolver}. * <p>This method cannot be overridden; use {@link #configureMessageConverters} instead. * Also see {@link #addDefaultHttpMessageConverters} for adding default message converters. */ 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; }
/** * Override this method to add custom {@link HttpMessageConverter HttpMessageConverters} * to use with the {@link RequestMappingHandlerAdapter} and the * {@link ExceptionHandlerExceptionResolver}. * <p>Adding converters to the list turns off the default converters that would * otherwise be registered by default. Also see {@link #addDefaultHttpMessageConverters} * for adding default message converters. * @param converters a list to add message converters to (initially an empty list) */ protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) { } /** * Override this method to extend or modify the list of converters after it has * been configured. This may be useful for example to allow default converters * to be registered and then insert a custom converter through this method. * @param converters the list of configured converters to extend * @since 4.1.3 */ protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) { }
这里的configureMessageConverters我自己已经实现,但这样做的结果是会 turns off the default converters
/** * Adds a set of default HttpMessageConverter instances to the given list. * Subclasses can call this method from {@link #configureMessageConverters}. * @param messageConverters the list to add the default message converters to */ protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(new ByteArrayHttpMessageConverter()); messageConverters.add(new StringHttpMessageConverter()); messageConverters.add(new ResourceHttpMessageConverter()); messageConverters.add(new ResourceRegionHttpMessageConverter()); if (!shouldIgnoreXml) { try { messageConverters.add(new SourceHttpMessageConverter<>()); } catch (Throwable ex) { // Ignore when no TransformerFactory implementation is available... } } messageConverters.add(new AllEncompassingFormHttpMessageConverter()); if (romePresent) { messageConverters.add(new AtomFeedHttpMessageConverter()); messageConverters.add(new RssChannelHttpMessageConverter()); } if (!shouldIgnoreXml) { if (jackson2XmlPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build())); } else if (jaxb2Present) { messageConverters.add(new Jaxb2RootElementHttpMessageConverter()); } } if (jackson2Present) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build())); } else if (gsonPresent) { messageConverters.add(new GsonHttpMessageConverter()); } else if (jsonbPresent) { messageConverters.add(new JsonbHttpMessageConverter()); } else if (kotlinSerializationJsonPresent) { messageConverters.add(new KotlinSerializationJsonHttpMessageConverter()); } if (jackson2SmilePresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build())); } if (jackson2CborPresent) { Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor(); if (this.applicationContext != null) { builder.applicationContext(this.applicationContext); } messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build())); } }
这里可以看到MappingJackson2XmlHttpMessageConverter已经被添加,而且点进去看:
/** * Construct a new {@code MappingJackson2XmlHttpMessageConverter} with a custom {@link ObjectMapper} * (must be a {@link XmlMapper} instance). * You can use {@link Jackson2ObjectMapperBuilder} to build it easily. * @see Jackson2ObjectMapperBuilder#xml() */ public MappingJackson2XmlHttpMessageConverter(ObjectMapper objectMapper) { super(objectMapper, new MediaType("application", "xml", StandardCharsets.UTF_8), new MediaType("text", "xml", StandardCharsets.UTF_8), new MediaType("application", "*+xml", StandardCharsets.UTF_8)); Assert.isInstanceOf(XmlMapper.class, objectMapper, "XmlMapper required"); }
原来 default converters已经支持了xml/text这样的请求。是因为跳过了默认设置所以没生效。所以接下来要做的就是去掉configureMessageConverters。那Long型精度问题怎么解决?原来是在configureMessageConverters里设置的。可以
重写extendMessageConverters这个方法,下面是最终代码:
package com.davidhu.shopguide.web.config; //import javax.annotation.Resource; //import com.davidhu.shopguide.web.interceptor.CorsInterceptor; import com.davidhu.shopguide.web.interceptor.CorsInterceptor; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.http.converter.*; import org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter; import org.springframework.web.servlet.config.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import javax.annotation.Resource; import java.nio.charset.Charset; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.List; /** * @author <a href="hugaoxiang8619@adpanshi.com">hugaoxiang</a> * @version V1.0.1 * @Description * @date 2019年5月9日 下午7:16:15 */ @Configuration public class WebMvcConfig extends WebMvcConfigurationSupport { @Override public void configureContentNegotiation(final ContentNegotiationConfigurer configurer) { // Turn off suffix-based content negotiation configurer.favorPathExtension(false); } //用configureMessageConverters会使默认配置失效,用extendMessageConverters解决Long型精度丢失问题 @Override public void extendMessageConverters(List<HttpMessageConverter<?>> converters) { for(HttpMessageConverter converter: converters) { if(converter instanceof MappingJackson2HttpMessageConverter) { ObjectMapper objectMapper = ((MappingJackson2HttpMessageConverter) converter).getObjectMapper(); SimpleModule simpleModule = new SimpleModule(); simpleModule.addSerializer(Long.class, ToStringSerializer.instance); simpleModule.addSerializer(Long.TYPE, ToStringSerializer.instance); objectMapper.registerModule(simpleModule); ((MappingJackson2HttpMessageConverter) converter).setObjectMapper(objectMapper); } } } @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("*").allowCredentials(true).allowedHeaders("*").maxAge(3600); } }
=========================
经过这番改动,又发现以前好的上传微信收款码功能返回的是个xml不是json。因为这个请求的Accept:*/*,只能在相应接口加上 produces = {MediaType.APPLICATION_JSON_VALUE}