内容协商 Spring ContentNegotiation

在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

24ac48f6a2a3d1e6482c5cca598312a6.jpeg

  • 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部分的内容协商功能,有兴趣的小伙伴可以查看部分的源码。 

以上便是今天的全部内容,感谢关注,欢迎小伙伴留言反馈。

往期回顾:

d1ba59b090e3dc8b38d35cec39422a76.jpeg

SpringAutowired

7a7fe74450ea87170e1380a05b466883.jpeg4b1f4f0729296abed40c66b59746d928.png

长按,识别二维码,加关注

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值