ParameterContentNegotiationStrategy
上面抽象类的子类具体实现,从名字中能看出扩展名来自于param参数。
public class ParameterContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { // 请求参数默认的key是format,你是可以设置和更改的。(set方法) private String parameterName = "format"; // 唯一构造 public ParameterContentNegotiationStrategy(Map<String, MediaType> mediaTypes) { super(mediaTypes); } ... // 生路get/set // 小Tips:这里调用的是getParameterName()而不是直接用属性名,以后建议大家设计框架也都这么使用 虽然很多时候效果是一样的,但更符合使用规范 @Override @Nullable protected String getMediaTypeKey(NativeWebRequest request) { return request.getParameter(getParameterName()); } }
根据一个查询参数(query parameter)判断请求的MediaType,该查询参数缺省使用format。
需要注意的是:基于param的此策略Spring MVC虽然支持,但默认是木有开启的,若想使用需要手动显示开启
PathExtensionContentNegotiationStrategy
它的扩展名需要从Path里面分析出来。
public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy { private UrlPathHelper urlPathHelper = new UrlPathHelper(); // 它额外提供了一个空构造 public PathExtensionContentNegotiationStrategy() { this(null); } // 有参构造 public PathExtensionContentNegotiationStrategy(@Nullable Map<String, MediaType> mediaTypes) { super(mediaTypes); setUseRegisteredExtensionsOnly(false); setIgnoreUnknownExtensions(true); // 注意:这个值设置为了true this.urlPathHelper.setUrlDecode(false); // 不需要解码(url请勿有中文) } // @since 4.2.8 可见Spring MVC允许你自己定义解析的逻辑 public void setUrlPathHelper(UrlPathHelper urlPathHelper) { this.urlPathHelper = urlPathHelper; } @Override @Nullable protected String getMediaTypeKey(NativeWebRequest webRequest) { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); if (request == null) { return null; } // 借助urlPathHelper、UriUtils从URL中把扩展名解析出来 String path = this.urlPathHelper.getLookupPathForRequest(request); String extension = UriUtils.extractFileExtension(path); return (StringUtils.hasText(extension) ? extension.toLowerCase(Locale.ENGLISH) : null); } // 子类ServletPathExtensionContentNegotiationStrategy有使用和复写 // 它的作用是面向Resource找到这个资源对应的MediaType ~ @Nullable public MediaType getMediaTypeForResource(Resource resource) { ... } }
根据请求URL路径中所请求的文件资源的扩展名部分判断请求的MediaType(借助UrlPathHelper和UriUtils解析URL)。
ServletPathExtensionContentNegotiationStrategy
它是对PathExtensionContentNegotiationStrategy的扩展,和Servlet容器有关了。因为Servlet额外提供了这个方法:ServletContext#getMimeType(String)来处理文件的扩展名问题。
public class ServletPathExtensionContentNegotiationStrategy extends PathExtensionContentNegotiationStrategy { private final ServletContext servletContext; ... // 省略构造函数 // 一句话:在去工厂找之前,先去this.servletContext.getMimeType("file." + extension)这里找一下,找到就直接返回。否则再进工厂 @Override @Nullable protected MediaType handleNoMatch(NativeWebRequest webRequest, String extension) throws HttpMediaTypeNotAcceptableException { ... } // 一样的:先this.servletContext.getMimeType(resource.getFilename()) 再交给父类处理 @Override public MediaType getMediaTypeForResource(Resource resource) { ... } // 两者调用父类的条件都是:mediaType == null || MediaType.APPLICATION_OCTET_STREAM.equals(mediaType) }
说明:ServletPathExtensionContentNegotiationStrategy
是Spring MVC
默认就开启支持的策略,无需手动开启。
FixedContentNegotiationStrategy
固定类型解析:返回固定
的MediaType。
public class FixedContentNegotiationStrategy implements ContentNegotiationStrategy { private final List<MediaType> contentTypes; // 构造函数:必须指定MediaType // 一般通过@RequestMapping.produces这个注解属性指定(可指定多个) public FixedContentNegotiationStrategy(MediaType contentType) { this(Collections.singletonList(contentType)); } // @since 5.0 public FixedContentNegotiationStrategy(List<MediaType> contentTypes) { this.contentTypes = Collections.unmodifiableList(contentTypes); } }
固定参数类型非常简单,构造函数传进来啥返回啥(不能为null)。
ContentNegotiationManager
介绍完了上面4中协商策略,开始介绍这个协商"容器"。
这个管理器它的作用特别像之前讲述的xxxComposite这种“容器”管理类,总体思想是管理、委托,有了之前的基础了解起他还是非常简单的了。
// 它不仅管理一堆strategies(List),还管理一堆resolvers(Set) public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver { private final List<ContentNegotiationStrategy> strategies = new ArrayList<>(); private final Set<MediaTypeFileExtensionResolver> resolvers = new LinkedHashSet<>(); ... // 若没特殊指定,至少是包含了这一种的策略的:HeaderContentNegotiationStrategy public ContentNegotiationManager() { this(new HeaderContentNegotiationStrategy()); } ... // 因为比较简单,所以省略其它代码 }
它是一个ContentNegotiationStrategy容器,同时也是一个MediaTypeFileExtensionResolver容器。自身同时实现了这两个接口。
ContentNegotiationManagerFactoryBean
顾名思义,它是专门用于来创建一个ContentNegotiationManager的FactoryBean。
// @since 3.2 还实现了ServletContextAware,可以得到当前servlet容器上下文 public class ContentNegotiationManagerFactoryBean implements FactoryBean<ContentNegotiationManager>, ServletContextAware, InitializingBean { // 默认就是开启了对后缀的支持的 private boolean favorPathExtension = true; // 默认没有开启对param的支持 private boolean favorParameter = false; // 默认也是开启了对Accept的支持的 private boolean ignoreAcceptHeader = false; private Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>(); private boolean ignoreUnknownPathExtensions = true; // Jaf是一个数据处理框架,可忽略 private Boolean useJaf; private String parameterName = "format"; private ContentNegotiationStrategy defaultNegotiationStrategy; private ContentNegotiationManager contentNegotiationManager; private ServletContext servletContext; ... // 省略普通的get/set // 注意这里传入的是:Properties 表示后缀和MediaType的对应关系 public void setMediaTypes(Properties mediaTypes) { if (!CollectionUtils.isEmpty(mediaTypes)) { for (Entry<Object, Object> entry : mediaTypes.entrySet()) { String extension = ((String)entry.getKey()).toLowerCase(Locale.ENGLISH); MediaType mediaType = MediaType.valueOf((String) entry.getValue()); this.mediaTypes.put(extension, mediaType); } } } public void addMediaType(String fileExtension, MediaType mediaType) { this.mediaTypes.put(fileExtension, mediaType); } ... // 这里面处理了很多默认逻辑 @Override public void afterPropertiesSet() { List<ContentNegotiationStrategy> strategies = new ArrayList<ContentNegotiationStrategy>(); // 默认favorPathExtension=true,所以是支持path后缀模式的 // servlet环境使用的是ServletPathExtensionContentNegotiationStrategy,否则使用的是PathExtensionContentNegotiationStrategy // if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy; if (this.servletContext != null && !isUseJafTurnedOff()) { strategy = new ServletPathExtensionContentNegotiationStrategy(this.servletContext, this.mediaTypes); } else { strategy = new PathExtensionContentNegotiationStrategy(this.mediaTypes); } strategy.setIgnoreUnknownExtensions(this.ignoreUnknownPathExtensions); if (this.useJaf != null) { strategy.setUseJaf(this.useJaf); } strategies.add(strategy); } // 默认favorParameter=false 木有开启滴 if (this.favorParameter) { ParameterContentNegotiationStrategy strategy = new ParameterContentNegotiationStrategy(this.mediaTypes); strategy.setParameterName(this.parameterName); strategies.add(strategy); } // 注意这前面有个!,所以默认Accept也是支持的 if (!this.ignoreAcceptHeader) { strategies.add(new HeaderContentNegotiationStrategy()); } // 若你喜欢,你可以设置一个defaultNegotiationStrategy 最终也会被add进去 if (this.defaultNegotiationStrategy != null) { strategies.add(this.defaultNegotiationStrategy); } // 这部分我需要提醒注意的是:这里使用的是ArrayList,所以你add的顺序就是u最后的执行顺序 // 所以若你指定了defaultNegotiationStrategy,它也是放到最后的 this.contentNegotiationManager = new ContentNegotiationManager(strategies); } // 三个接口方法 @Override public ContentNegotiationManager getObject() { return this.contentNegotiationManager; } @Override public Class<?> getObjectType() { return ContentNegotiationManager.class; } @Override public boolean isSingleton() { return true; } }