ContentNegotiationManager
是Spring Web
一个重要的工具类,用于判断一个请求的媒体类型MediaType
列表。具体的做法是委托给它所维护的一组ContentNegotiationStrategy
实例。实际上它自身也实现了接口ContentNegotiationStrategy
,使用者可以直接将它作为一个ContentNegotiationStrategy
使用。
另外,ContentNegotiationManager
也实现了接口MediaTypeFileExtensionResolver
,从而可以根据MediaType
查找到相应的文件扩展名。这一点也是通过将任务委托给他所维护的一组MediaTypeFileExtensionResolver
实例完成的。
源代码
源代码版本 : Spring Security Config 5.1.4.RELEASE
package org.springframework.web.accept;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.context.request.NativeWebRequest;
/**
* Central class to determine requested MediaType media types
* for a request. This is done by delegating to a list of configured
* ContentNegotiationStrategy instances.
*
* Also provides methods to look up file extensions for a media type.
* This is done by delegating to the list of configured
* MediaTypeFileExtensionResolver instances.
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @since 3.2
*/
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
// 所维护的 ContentNegotiationStrategy 实例列表
private final List<ContentNegotiationStrategy> strategies = new ArrayList<>();
// 所维护的 MediaTypeFileExtensionResolver 实例集合
private final Set<MediaTypeFileExtensionResolver> resolvers = new LinkedHashSet<>();
/**
* 构造函数
* Create an instance with the given list of
* ContentNegotiationStrategy strategies each of which may also be
* an instance of MediaTypeFileExtensionResolver.
* @param strategies the strategies to use
*/
public ContentNegotiationManager(ContentNegotiationStrategy... strategies) {
this(Arrays.asList(strategies));
}
/**
* 构造函数
* A collection-based alternative to
* #ContentNegotiationManager(ContentNegotiationStrategy...).
* @param strategies the strategies to use
* @since 3.2.2
*/
public ContentNegotiationManager(Collection<ContentNegotiationStrategy> strategies) {
Assert.notEmpty(strategies, "At least one ContentNegotiationStrategy is expected");
this.strategies.addAll(strategies);
for (ContentNegotiationStrategy strategy : this.strategies) {
if (strategy instanceof MediaTypeFileExtensionResolver) {
this.resolvers.add((MediaTypeFileExtensionResolver) strategy);
}
}
}
/**
* 缺省构造函数,这种情况下仅维护一个 ContentNegotiationStrategy : HeaderContentNegotiationStrategy
* Create a default instance with a HeaderContentNegotiationStrategy.
*/
public ContentNegotiationManager() {
this(new HeaderContentNegotiationStrategy());
}
/**
* Return the configured content negotiation strategies.
* @since 3.2.16
*/
public List<ContentNegotiationStrategy> getStrategies() {
return this.strategies;
}
/**
* Find a ContentNegotiationStrategy of the given type.
* @param strategyType the strategy type
* @return the first matching strategy, or null if none
* @since 4.3
*/
@SuppressWarnings("unchecked")
@Nullable
public <T extends ContentNegotiationStrategy> T getStrategy(Class<T> strategyType) {
for (ContentNegotiationStrategy strategy : getStrategies()) {
if (strategyType.isInstance(strategy)) {
return (T) strategy;
}
}
return null;
}
/**
* 添加新的 MediaTypeFileExtensionResolver 实例
*
* Register more MediaTypeFileExtensionResolver instances in addition
* to those detected at construction.
* @param resolvers the resolvers to add
*/
public void addFileExtensionResolvers(MediaTypeFileExtensionResolver... resolvers) {
Collections.addAll(this.resolvers, resolvers);
}
// 接口 ContentNegotiationStrategy 定义的方法
// 获取一个请求对象的MediaType列表,具体任务是依次询问所维护的每个ContentNegotiationStrategy实例,
// 如果谁能获取该请求的MediaType列表则返回并使用该结果,否则最终使用 */*
@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;
}
// 接口 MediaTypeFileExtensionResolver 所定义的方法
// 获取指定媒体类型所对应的扩展名,具体做法是询问所维护的每个MediaTypeFileExtensionResolver获取它们
// 所知道的该媒体类型所对应的扩展名,最终取这些扩展名的并集
@Override
public List<String> resolveFileExtensions(MediaType mediaType) {
Set<String> result = new LinkedHashSet<>();
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
result.addAll(resolver.resolveFileExtensions(mediaType));
}
return new ArrayList<>(result);
}
/**
* 接口 MediaTypeFileExtensionResolver 所定义的方法 : 获取所注册的所有文件扩展名
*
* 启动时该方法返回PathExtensionContentNegotiationStrategy/PathExtensionContentNegotiationStrategy/
* ParameterContentNegotiationStrategy 所注册的扩展名。而运行时如果useRegisteredExtensionsOnly为false,
* 某个ContentNegotiationStrategy的解析逻辑可能会导致该结果集发生变化。
*
* At startup this method returns extensions explicitly registered with
* either PathExtensionContentNegotiationStrategy or
* ParameterContentNegotiationStrategy. At runtime if there is a
* "path extension" strategy and its
* PathExtensionContentNegotiationStrategy#setUseRegisteredExtensionsOnly(boolean)
* useRegisteredExtensionsOnly property is set to "false", the list of extensions may
* increase as file extensions are resolved via
* org.springframework.http.MediaTypeFactory and cached.
*/
@Override
public List<String> getAllFileExtensions() {
Set<String> result = new LinkedHashSet<>();
for (MediaTypeFileExtensionResolver resolver : this.resolvers) {
result.addAll(resolver.getAllFileExtensions());
}
return new ArrayList<>(result);
}
}
从源代码不难看出,ContentNegotiationManager
其实就是一个组合容器:
- 组合了一组用于判断请求媒体类型的
ContentNegotiationStrategy
实例; - 组合了一组用于根据媒体类型获取文件扩展名的
MediaTypeFileExtensionResolver
实例;
而ContentNegotiationManager
又实现了这两个接口ContentNegotiationStrategy
/MediaTypeFileExtensionResolver
。它对这两个接口的功能实现最终委托给了它所维护的这些实例来完成。通过这种组合-委托模式,使用者自己不用维护这些单个的ContentNegotiationStrategy
/MediaTypeFileExtensionResolver
的实例以及对它们的组合使用了,而是只需要通过使用ContentNegotiationManager
即可。