Spring版本5.1.4.release.
内容协商是用在Springmvc返回Controller方法结果序列化时使用,而不是解析mvc参数时使用。
Springmvc支持4种内容协商,拓展名、固定值、Http的头部Accept、请求参数format,那Springmvc中怎么实现的呢,怎么使用已经有很多人分析了,这里来分析下怎么实现的。
RequestResponseBodyMethodProcessor#handleReturnValue中调用了父类的writeWithMessageConverters,AbstractMessageConverterMethodProcessor#writeWithMessageConverters()中调用了getAcceptableMediaTypes,如下List-1
List-1
1private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request) 2 throws HttpMediaTypeNotAcceptableException { 3 return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request)); 4}
List-2中,ContentNegotiationManager的resolveMediaTypes方法中循坏遍历ContentNegotiationStrategy,分别调用其resolveMediaTypes方法。
List-2
1@Override 2public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { 3 for (ContentNegotiationStrategy strategy : this.strategies) { 4 List<MediaType> mediaTypes = strategy.resolveMediaTypes(request); 5 if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) { 6 continue; 7 } 8 return mediaTypes; 9 } 10 return MEDIA_TYPE_ALL_LIST; 11}
ContentNegotiationManager是由ContentNegotiationManagerFactoryBean创建的,如下图1所示,ContentNegotiationManagerFactoryBean实现了FactoryBean,通过getObject方法获取创建好后的ContentNegotiationManager,实现接口ServletContextAware是为了获取ServletContext从而构造ServletPathExtensionContentNegotiationStrategy,至于实现InitializingBean则是用afterPropertiesSet进行初始化ContentNegotiationManager

图1
List-3
1public ContentNegotiationManager build() { 2 List<ContentNegotiationStrategy> strategies = new ArrayList<>(); 3 4 if (this.strategies != null) { 5 strategies.addAll(this.strategies); 6 } 7 else { 8 //1 9 if (this.favorPathExtension) { 10 PathExtensionContentNegotiationStrategy strategy; 11 if (this.servletContext != null && !useRegisteredExtensionsOnly()) { 12 strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes); 13 } 14 else { 15 strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); 16 } 17 strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions); 18 if (this.useRegisteredExtensionsOnly != null) { 19 strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly); 20 } 21 strategies.add(strategy); 22 } 23 //2 24 if (this.favorParameter) { 25 ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes); 26 strategy.setParameterName(this.parameterName); 27 if (this.useRegisteredExtensionsOnly != null) { 28 strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly); 29 } 30 else { 31 strategy.setUseRegisteredExtensionsOnly(true); // backwards compatibility 32 } 33 strategies.add(strategy); 34 } 35 //3 36 if (!this.ignoreAcceptHeader) { 37 strategies.add(new HeaderContentNegotiationStrategy()); 38 } 39 //4 40 if (this.defaultNegotiationStrategy != null) { 41 strategies.add(this.defaultNegotiationStrategy); 42 } 43 } 44 45 this.contentNegotiationManager = new ContentNegotiationManager(strategies); 46 return this.contentNegotiationManager; 47}
如上List-3所示,
- favorPathExtension是true,构造PathExtensionContentNegotiationStrategy,并加到结果结合strategies中,默认ServletPathExtensionContentNegotiationStrategy是不会构造的,除非我们手动的设置
- favorParameter是false,如果我们设置为true后,会构造ParameterContentNegotiationStrategy,即我们设置的format=json会生效
- ignoreAcceptHeader是false,所以会把HeaderContentNegotiationStrategy加入到结果集合中,即Http头部的Accept
- 如果设置了defaultNegotiationStrategy,就会把我们添加的自定义Strategy加入到结果集合中
如下List-4是ContentNegotiationManager的resolveMediaTypes方法,顺序遍历strategy,如果resolveMediaTypes返回的值不等于MEDIA_TYPE_ALL_LIST,那么就直接返回,结合List-3中添加的顺序,这就是路径拓展第一生效,第二format固定值,第三Http头部的Accept
List-4
1@Override 2public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException { 3 for (ContentNegotiationStrategy strategy : this.strategies) { 4 List<MediaType> mediaTypes = strategy.resolveMediaTypes(request); 5 if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) { 6 continue; 7 } 8 return mediaTypes; 9 } 10 return MEDIA_TYPE_ALL_LIST; 11}
ContentNegotiationStrategy使用了策略模式,在HeaderContentNegotiationStrategy中,从Http头部拿到Accept值后,还对结果按权重进行排序,这是有对应的JSR规范描述这个权重的计算的