在SpringMVC中,我们访问一个 RESTful @ReponseBody 接口时,spring可以实现根据path extension来给出不同的响应格式,如:
// Json
curl http://localhost:8080/springautowired/foo.json
{"foo":"bar"}
// Xml
curl http://localhost:8080/springqutowired/foo.xml
<?xml version="1.0" encoding="UTF-8"?>
<root><foo>bar</foo></root>
这是spring内容协商的一种外在表现形式。
内容协商,ContentNegotiation 在spring中关注的核心问题是: 你想要什么样格式的数据 。 你想要什么,可以理解为, http请求响应时,服务器应该往http response body里写什么格式的数据 。
分层概念
在SpringMVC的概念中,View指的是数据的展现形式,Model可以理解成领域数据模型或者载体,MVC的核心是分层,分层的目的是为了解耦。就View与Model之间的解耦,指的是数据本身与数据展示形式的解耦,在spring应用内部,领域数据的载体,可以简单理解为bean。到了View层,数据的展现形式多种多样,可以是Jsp, velocity,pdf 或者RESTful的json, xml等。
使用SpringMVC时,通常有两种方式产生输出:
使用RESTful 的 @ ResponseBody,借助于HttpMessageConverter来输出像Json Xml等类型的的数据
使用 view resolution,可以生成更传统的Html页面(Jsp、Velocity等)
无论用哪种方式,你都有可能需要把Controller返回的相同的数据内容转成不同的表现形式。
在之前的内容中有提到HttpMessageConverter是spring处理rest请求时负责解析与转换http消息的逻辑单元。借助HttpMessageConverter,sping可以实现应用领域数据bean与http消息body的转换。 那么问题来了,当前一个http请求完成时,该用哪个具体的HttpMessageConverter实现来回写数据,这就是ContentNegotiation关注的内容。
协商策略
提到协商,必须有协商的点和策略,对应一个http请求,spring来判定请求中htto body格式主要通过以下三个因素(优先级同顺序):
path extension 也称为path suffix 就是文章开头提到的例子中的url后缀 .json .xml
url parameter 是一个明确指定媒体类型的参数,即通过显式的参数告诉服务器,我想要什么格式的数据。如format=json, 参数名可指定,默认是format
http header ( Accept) 如果上边两项都没有,能参考http header Accept来判定响应数据格式,因某些浏览器或者有些http请求不完全按规则来指定需要的媒体类型,所以使用时需要谨慎使用header
上述协商的规则,在spring中被抽象为接口:
// A strategy for resolving the requested media types
// for a request.
ContentNegotiationStrategy
FixContentNegotiationStrategy一般用来处理默认数据格式
HeaderContentNegotiationStrategy用来处理http Accept header的方式
ParameterContentNegotiationStrategy用来处理显式的媒体类型参数方式
PathExtensionContentNegotiationStrategy处理url后缀方式
上边这些strategy实现类以组合模式的形式封装成ContentNegotiationManager,对外提供逻辑接口。
ContentNegotiationManager的resolveMediaTypes方法如下:
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request)
throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
continue;
}
return mediaTypes;
}
return Collections.emptyList();
}
使用方式
在项目中,可以通过ContentNegotiationManagerFactoryBean来配置一个全局的内容协商contentNegotiationManager,给spring MVC使用,ContentNegotiationManagerFactoryBean中主要属性如下:
public class ContentNegotiationManagerFactoryBean
implements FactoryBean<ContentNegotiationManager>,
ServletContextAware, InitializingBean {
//是否关注url路径后缀
private boolean favorPathExtension = true;
//是否关注媒体参数
private boolean favorParameter = false;
//是否忽略http Accept header
private boolean ignoreAcceptHeader = false;
// url 后缀与媒体类型的映射
private Map<String, MediaType> mediaTypes =
new HashMap<String, MediaType>();
//忽略未知的后缀
private boolean ignoreUnknownPathExtensions = true;
// 默认的媒体参数名
private String parameterName = "format”;
可以这样定义一个contentNegotiationManager:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true)
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
xml形式的配置:
<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="false" />
<property name="favorParameter" value="true" />
<property name="parameterName" value="mediaType" />
<property name="ignoreAcceptHeader" value="true"/>
<property name="defaultContentType" value="application/json" />
<property name="mediaTypes">
<map>
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
ContentNegotiation不仅限于Rest风格的http请求,SpringMVC中还有ContentNegotiatingViewResolver完成了viewResolver部分的内容协商功能,有兴趣的小伙伴可以查看部分的源码。
以上便是今天的全部内容,感谢关注,欢迎小伙伴留言反馈。
往期回顾:
Spring Environment Abstraction
Spring MVC 异常处理机制
Spring 重试机制实现原理
Spring Cache的使用及实现原理
Spring AOP 模块概述
Spring RestTemplate详解
Spring类型转换机制
Spring中的异步Servlet
玩转Spring bean的终极利器 Spring的注入方式大比拼
Spring bean生命周期不可不知的接口
SpringAutowired
长按,识别二维码,加关注