【Spring】抽丝剥茧SpringMVC-ContentNegotiationManager

本文源码基于SpringMVC 5.2.7

ContentNegotiationManager

ContentNegotiationManager也是SpringMVC的重要功能性组件。其提供解析requst可接受的MediaType以及根据MediaType解析对应文件扩展名。

ContentNegotiationManager包含2个重要的成员变量

  • strategies:ContentNegotiationStrategy类型集合,就是用来解析MediaType
  • resolvers:MediaTypeFileExtensionResolver类型集合,用来解析MediaType对应的文件扩展名

ContentNegotiationManager本身也实现这两个接口ContentNegotiationStrategy、MediaTypeFileExtensionResolver。ContentNegotiationManager在解析MediaType时就是遍历strategies依次解析。同样,在解析扩展名的时候也是遍历resolvers

public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
    ... ...
    //笔者注:遍历strategies解析request可接受的MediaTypes
    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;
	}

    //笔者注:遍历resolvers解析MediaType对应的文件扩展名
    private List<String> doResolveExtensions(Function<MediaTypeFileExtensionResolver, List<String>> extractor) {
		List<String> result = null;
		for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
			List<String> extensions = extractor.apply(resolver);
			if (CollectionUtils.isEmpty(extensions)) {
				continue;
			}
			result = (result != null ? result : new ArrayList<>(4));
			for (String extension : extensions) {
				if (!result.contains(extension)) {
					result.add(extension);
				}
			}
		}
		return (result != null ? result : Collections.emptyList());
	}

.... ....

}

在SpringMVC框架中,ContentNegotiationManager主要用在以下3种场合:

其一,在RequestMappingInfoHandlerMapping寻找匹配的RequestMappingInfo时有一种匹配条件是ProducesRequestCondition。这个条件需要获取request可接受的MediaType。这时就是使用ContentNegotiationManager来解析:

public final class ProducesRequestCondition extends AbstractRequestCondition<ProducesRequestCondition> {
... ...

    private List<MediaType> getAcceptedMediaTypes(HttpServletRequest request)
			throws HttpMediaTypeNotAcceptableException {

		List<MediaType> result = (List<MediaType>) request.getAttribute(MEDIA_TYPES_ATTRIBUTE);
		if (result == null) {

            //笔者注:contentNegotiationManager解析request可接受的MediaTypes
			result = this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
			request.setAttribute(MEDIA_TYPES_ATTRIBUTE, result);
		}
		return result;
	}

... ...
}

其二,返回处理器HttpEntityMethodProcessor、RequestResponseBodyMethodProcessor往response写数据时需要判断request可接受MediaType与@RequestMapping属性produces指定的MediaTypes是否兼容

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver
		implements HandlerMethodReturnValueHandler {

... ...

    protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
    ... ...
		   //笔者注:获取acceptableTypes
			List<MediaType> acceptableTypes = getAcceptableMediaTypes(request);
			List<MediaType> producibleTypes = getProducibleMediaTypes(request, valueType, targetType);

			if (body != null && producibleTypes.isEmpty()) {
				throw new HttpMessageNotWritableException(
						"No converter found for return value of type: " + valueType);
			}

            //笔者注:遍历acceptableTypes中与producibleTypes兼容的MediaType
			List<MediaType> mediaTypesToUse = new ArrayList<>();
			for (MediaType requestedType : acceptableTypes) {
				for (MediaType producibleType : producibleTypes) {
					if (requestedType.isCompatibleWith(producibleType)) {
						mediaTypesToUse.add(getMostSpecificMediaType(requestedType, producibleType));
					}
				}
			}
    ... ...
    }

    //笔者注:获取acceptableTypes
    private List<MediaType> getAcceptableMediaTypes(HttpServletRequest request)
			throws HttpMediaTypeNotAcceptableException {

		return this.contentNegotiationManager.resolveMediaTypes(new ServletWebRequest(request));
	}
}

其三,在ContentNegotiatingViewResolver视图解析器根据viewName解析view时也需要根据request的可接受MediaType解出最佳视图

public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
		implements ViewResolver, Ordered, InitializingBean {
... ...
    public View resolveViewName(String viewName, Locale locale) throws Exception {
		RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
		Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
        //笔者注:获取request可接受的MediaTypes
		List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
		if (requestedMediaTypes != null) {
			//笔者注:获取候选视图集合
            List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
            //笔者注:根据requestedMediaTypes获取最佳视图
			View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
			if (bestView != null) {
				return bestView;
			}
		}
    ... ...
    }

   //笔者注:获取acceptableMediaTypes与producibleMediaTypes兼容的MediaTypes
    protected List<MediaType> getMediaTypes(HttpServletRequest request) {
		Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
		try {
			ServletWebRequest webRequest = new ServletWebRequest(request);
            //笔者注:解析出request可接受的acceptableMediaTypes 
			List<MediaType> acceptableMediaTypes = this.contentNegotiationManager.resolveMediaTypes(webRequest);
			List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request);
			Set<MediaType> compatibleMediaTypes = new LinkedHashSet<>();
			for (MediaType acceptable : acceptableMediaTypes) {
				for (MediaType producible : producibleMediaTypes) {
					if (acceptable.isCompatibleWith(producible)) {
						compatibleMediaTypes.add(getMostSpecificMediaType(acceptable, producible));
					}
				}
			}
			List<MediaType> selectedMediaTypes = new ArrayList<>(compatibleMediaTypes);
			MediaType.sortBySpecificityAndQuality(selectedMediaTypes);
			return selectedMediaTypes;
		}
		catch (HttpMediaTypeNotAcceptableException ex) {
			if (logger.isDebugEnabled()) {
				logger.debug(ex.getMessage());
			}
			return null;
		}
	}

    //笔者注:获取候选视图
    private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
			throws Exception {

		List<View> candidateViews = new ArrayList<>();
		if (this.viewResolvers != null) {
			Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
			for (ViewResolver viewResolver : this.viewResolvers) {
				View view = viewResolver.resolveViewName(viewName, locale);
				if (view != null) {
					candidateViews.add(view);
				}
				for (MediaType requestedMediaType : requestedMediaTypes) {
                    
                    //笔者注:根据MediaType获取对应的文件扩展名
					List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
					for (String extension : extensions) {
						String viewNameWithExtension = viewName + '.' + extension;
						view = viewResolver.resolveViewName(viewNameWithExtension, locale);
						if (view != null) {
							candidateViews.add(view);
						}
					}
				}
			}
		}
		if (!CollectionUtils.isEmpty(this.defaultViews)) {
			candidateViews.addAll(this.defaultViews);
		}
		return candidateViews;
	}
}

常见的ContentNegotiationStrategy

ParameterContentNegotiationStrategy

根据请求参数指定的文件扩展名获取MediaType。ParameterContentNegotiationStrategy类结构如下:

public abstract class AbstractMappingContentNegotiationStrategy extends MappingMediaTypeFileExtensionResolver
		implements ContentNegotiationStrategy {
    ... ...    
    public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
			throws HttpMediaTypeNotAcceptableException {
        
//笔者注:getMediaTypeKey在子类ParameterContentNegotiationStrategy中的实现是获取request中参数"fomat"的值
		return resolveMediaTypeKey(webRequest, getMediaTypeKey(webRequest));
	}

	
	public List<MediaType> resolveMediaTypeKey(NativeWebRequest webRequest, @Nullable String key)
			throws HttpMediaTypeNotAcceptableException {

		if (StringUtils.hasText(key)) {
            
   //笔者注:lookupMediaType在父类MappingMediaTypeFileExtensionResolver中实现,根据文件扩展名获取MediaType。MappingMediaTypeFileExtensionResolver存储类这样的Map
			MediaType mediaType = lookupMediaType(key);
			if (mediaType != null) {
				handleMatch(key, mediaType);
				return Collections.singletonList(mediaType);
			}
            
//笔者注:如果没命中就会走默认的文件扩展名与MediaType匹配,并且添加到MappingMediaTypeFileExtensionResolver的映射中
			mediaType = handleNoMatch(webRequest, key);
			if (mediaType != null) {

                //笔者注:添加到映射中,供下次请求使用
				addMapping(key, mediaType);
				return Collections.singletonList(mediaType);
			}
		}
		return MEDIA_TYPE_ALL_LIST;
	}
    ... ...
}

 

FixedContentNegotiationStrategy

固定返回提前配置好的MediaTypes

public class FixedContentNegotiationStrategy implements ContentNegotiationStrategy {

	private final List<MediaType> contentTypes;

	@Override
	public List<MediaType> resolveMediaTypes(NativeWebRequest request) {
		return this.contentTypes;
	}

}

HeaderContentNegotiationStrategy

这种解析MediaType策略就是从request的请求头"Accept"中解析

public class HeaderContentNegotiationStrategy implements ContentNegotiationStrategy {

   
	public List<MediaType> resolveMediaTypes(NativeWebRequest request)
			throws HttpMediaTypeNotAcceptableException {
        //笔者注: 根据请求头"Accept"解析MediaType
		String[] headerValueArray = request.getHeaderValues(HttpHeaders.ACCEPT);
		if (headerValueArray == null) {
			return MEDIA_TYPE_ALL_LIST;
		}

		List<String> headerValues = Arrays.asList(headerValueArray);
		try {
			List<MediaType> mediaTypes = MediaType.parseMediaTypes(headerValues);
			MediaType.sortBySpecificityAndQuality(mediaTypes);
			return !CollectionUtils.isEmpty(mediaTypes) ? mediaTypes : MEDIA_TYPE_ALL_LIST;
		}
		catch (InvalidMediaTypeException ex) {
			throw new HttpMediaTypeNotAcceptableException(
					"Could not parse 'Accept' header " + headerValues + ": " + ex.getMessage());
		}
	}

}

 

常见的MediaTypeFileExtensionResolver

MappingMediaTypeFileExtensionResolver

MappingMediaTypeFileExtensionResolver存储了一个Map映射关系,解析时就是从Map里面查找

public class MappingMediaTypeFileExtensionResolver implements MediaTypeFileExtensionResolver {
    //笔者注:MediaType与文件扩展名的映射关系 
    private final ConcurrentMap<MediaType, List<String>> fileExtensions = new ConcurrentHashMap<>(64);

    ... ...    
    public List<String> resolveFileExtensions(MediaType mediaType) {
		List<String> fileExtensions = this.fileExtensions.get(mediaType);
		return (fileExtensions != null ? fileExtensions : Collections.emptyList());
	}
    ... ...
}


目录 目录

上一篇 视图解析及渲染ViewResolver&View

下一篇 HttpMessageConverter

再下一篇 请求间传递参数机制FlashMap

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值