这里解释了 该文 的顺序(后缀 > 请求参数 > HTTP首部Accept)现象。Spring MVC是通过它来创建ContentNegotiationManager进而管理协商策略的。
内容协商的配置:ContentNegotiationConfigurer
虽然说默认情况下Spring开启的协商支持能覆盖我们绝大部分应用场景了,但不乏有的时候我们也还是需要对它进行个性化的,那么这部分就讲解下对它的个性化配置~
ContentNegotiationConfigurer
它用于"收集"配置项,根据你提供的配置项来创建出一个ContentNegotiationManager。
public class ContentNegotiationConfigurer { private final ContentNegotiationManagerFactoryBean factory = new ContentNegotiationManagerFactoryBean(); private final Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>(); public ContentNegotiationConfigurer(@Nullable ServletContext servletContext) { if (servletContext != null) { this.factory.setServletContext(servletContext); } } // @since 5.0 public void strategies(@Nullable List<ContentNegotiationStrategy> strategies) { this.factory.setStrategies(strategies); } ... public ContentNegotiationConfigurer defaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) { this.factory.setDefaultContentTypeStrategy(defaultStrategy); return this; } // 手动创建出一个ContentNegotiationManager 此方法是protected // 唯一调用处是:WebMvcConfigurationSupport protected ContentNegotiationManager buildContentNegotiationManager() { this.factory.addMediaTypes(this.mediaTypes); return this.factory.build(); } }
ContentNegotiationConfigurer可以认为是提供一个设置ContentNegotiationManagerFactoryBean的入口(自己内容new了一个它的实例),最终交给WebMvcConfigurationSupport向容器内注册这个Bean:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware { ... // 请注意是BeanName为:mvcContentNegotiationManager // 若实在有需要,你是可以覆盖的~~~~ @Bean public ContentNegotiationManager mvcContentNegotiationManager() { if (this.contentNegotiationManager == null) { ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext); configurer.mediaTypes(getDefaultMediaTypes()); // 服务端默认支持的后缀名-->MediaType们~~~ // 这个方法就是回调我们自定义配置的protected方法~~~~ configureContentNegotiation(configurer); // 调用方法生成一个管理器 this.contentNegotiationManager = configurer.buildContentNegotiationManager(); } return this.contentNegotiationManager; } // 默认支持的协商MediaType们~~~~ protected Map<String, MediaType> getDefaultMediaTypes() { Map<String, MediaType> map = new HashMap<>(4); // 几乎不用 if (romePresent) { map.put("atom", MediaType.APPLICATION_ATOM_XML); map.put("rss", MediaType.APPLICATION_RSS_XML); } // 若导了jackson对xml支持的包,它就会被支持 if (jaxb2Present || jackson2XmlPresent) { map.put("xml", MediaType.APPLICATION_XML); } // jackson.databind就支持json了,所以此处一般都是满足的 // 额外还支持到了gson和jsonb。希望不久将来内置支持fastjson if (jackson2Present || gsonPresent || jsonbPresent) { map.put("json", MediaType.APPLICATION_JSON); } if (jackson2SmilePresent) { map.put("smile", MediaType.valueOf("application/x-jackson-smile")); } if (jackson2CborPresent) { map.put("cbor", MediaType.valueOf("application/cbor")); } return map; } ... }
Tips:
WebMvcConfigurationSupport
是@EnableWebMvc
导进去的。
配置实践
有了上面理论的支撑,那么使用Spring MVC
协商的最佳实践配置可参考如下(大多数情况下都无需配置):
@Configuration @EnableWebMvc public class WebMvcConfig extends WebMvcConfigurerAdapter { @Override public void configureContentNegotiation(ContentNegotiationConfigurer configurer) { configurer.favorParameter(true) //.parameterName("mediaType") //.defaultContentTypeStrategy(new ...) // 自定义一个默认的内容协商策略 //.ignoreAcceptHeader(true) // 禁用Accept协商方式 //.defaultContentType(MediaType.APPLICATION_JSON) // 它的效果是new FixedContentNegotiationStrategy(contentTypes) 增加了对固定策略的支 //.strategies(list); //.useRegisteredExtensionsOnly() //PathExtensionContentNegotiationStrategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly); ; } }
总结
本文从原理上分析了Spring MVC
对内容协商策略的管理、使用以及开放的配置,旨在做到心中有数,从而更好、更安全、更方便的进行扩展,对下文内容协商视图的理解有非常大的帮助作用,有兴趣的可持续关注~