首页
学习
活动
专区
圈层
工具
发布

SpringFramework之ContentNegotiation内容协商

    Spring版本5.1.4.release.

    内容协商是用在Springmvc返回Controller方法结果序列化时使用,而不是解析mvc参数时使用。

    Springmvc支持4种内容协商,拓展名、固定值、Http的头部Accept、请求参数format,那Springmvc中怎么实现的呢,怎么使用已经有很多人分析了,这里来分析下怎么实现的。

    RequestResponseBodyMethodProcessor#handleReturnValue中调用了父类的writeWithMessageConverters,AbstractMessageConverterMethodProcessor#writeWithMessageConverters()中调用了getAcceptableMediaTypes,如下List-1

List-1

代码语言:javascript
复制
private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
		throws HttpMediaTypeNotAcceptableException {
	return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
}

    List-2中,ContentNegotiationManager的resolveMediaTypes方法中循坏遍历ContentNegotiationStrategy,分别调用其resolveMediaTypes方法。

List-2

代码语言:javascript
复制
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
	for (ContentNegotiationStrategy strategy : this.strategies) {
		List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
		if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
			continue;
		}
		return mediaTypes;
	}
	return MEDIA_TYPE_ALL_LIST;
}

   ContentNegotiationManager是由ContentNegotiationManagerFactoryBean创建的,如下图1所示,ContentNegotiationManagerFactoryBean实现了FactoryBean,通过getObject方法获取创建好后的ContentNegotiationManager,实现接口ServletContextAware是为了获取ServletContext从而构造ServletPathExtensionContentNegotiationStrategy,至于实现InitializingBean则是用afterPropertiesSet进行初始化ContentNegotiationManager

                                                                                    图1

List-3

代码语言:javascript
复制
public ContentNegotiationManager build() {
	List<ContentNegotiationStrategy> strategies = new ArrayList<>();

	if (this.strategies != null) {
		strategies.addAll(this.strategies);
	}
	else {
        //1
		if (this.favorPathExtension) {
			PathExtensionContentNegotiationStrategy strategy;
			if (this.servletContext != null && !useRegisteredExtensionsOnly()) {
				strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes);
			}
			else {
				strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes);
			}
			strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions);
			if (this.useRegisteredExtensionsOnly != null) {
				strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
			}
			strategies.add(strategy);
		}
        //2
		if (this.favorParameter) {
			ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes);
			strategy.setParameterName(this.parameterName);
			if (this.useRegisteredExtensionsOnly != null) {
				strategy.setUseRegisteredExtensionsOnly(this.useRegisteredExtensionsOnly);
			}
			else {
				strategy.setUseRegisteredExtensionsOnly(true);  // backwards compatibility
			}
			strategies.add(strategy);
		}
        //3
		if (!this.ignoreAcceptHeader) {
			strategies.add(new HeaderContentNegotiationStrategy());
		}
        //4
		if (this.defaultNegotiationStrategy != null) {
			strategies.add(this.defaultNegotiationStrategy);
		}
	}

	this.contentNegotiationManager = new ContentNegotiationManager(strategies);
	return this.contentNegotiationManager;
}

    如上List-3所示,

  1. favorPathExtension是true,构造PathExtensionContentNegotiationStrategy,并加到结果结合strategies中,默认ServletPathExtensionContentNegotiationStrategy是不会构造的,除非我们手动的设置
  2. favorParameter是false,如果我们设置为true后,会构造ParameterContentNegotiationStrategy,即我们设置的format=json会生效
  3. ignoreAcceptHeader是false,所以会把HeaderContentNegotiationStrategy加入到结果集合中,即Http头部的Accept
  4. 如果设置了defaultNegotiationStrategy,就会把我们添加的自定义Strategy加入到结果集合中

    如下List-4是ContentNegotiationManager的resolveMediaTypes方法,顺序遍历strategy,如果resolveMediaTypes返回的值不等于MEDIA_TYPE_ALL_LIST,那么就直接返回,结合List-3中添加的顺序,这就是路径拓展第一生效,第二format固定值,第三Http头部的Accept

List-4

代码语言:javascript
复制
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
	for (ContentNegotiationStrategy strategy : this.strategies) {
		List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
		if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
			continue;
		}
		return mediaTypes;
	}
	return MEDIA_TYPE_ALL_LIST;
}

    ContentNegotiationStrategy使用了策略模式,在HeaderContentNegotiationStrategy中,从Http头部拿到Accept值后,还对结果按权重进行排序,这是有对应的JSR规范描述这个权重的计算的

下一篇
举报
领券