Spring MVC Content Negotiation

2017年11月15日 00:21:21 carl-zhao 阅读数:2983
 

Spring MVC有两种方式生成output的方法:

  • 你可以使用restful式的@responsebody方法和HTTP消息转换器,通常是返回像JSON或XML这样的数据格式。编程客户端、移动应用程序和启用AJAX的浏览器都是常见的客户端。
  • 你也可以使用视图解析。尽管视图完全能够生成JSON和XML(在我的下一篇文章中有更多的内容),但视图通常用于为传统的web应用程序生成类似HTML的表示格式。
  • 实际上还有第三种可能——一些应用程序需要两者,而Spring MVC很容易支持这样的组合。我们会在最后回到这个问题上。

在这两种情况下,你都需要处理由控制器返回的相同数据的多个表示(或视图)。计算出要返回的数据格式称为内容协商(Content Negotiation)。

有三种情况下,我们需要知道在HTTP响应中发送哪种类型的数据格式:

  • HttpMessageConverters:确定要使用的正确的转换器。
  • Request Mappings:将传入的HTTP请求映射到返回不同格式的不同方法。
  • View Resolution:选择正确的视图来使用。

确定用户请求的格式依赖于ContentNegotationStrategy。有默认的实现可用,但如果你愿意,也可以实现自己的实现。

在这篇文章中,我想讨论如何配置和使用Spring的内容协商,主要是使用HTTP消息转换器的RESTful控制器。在另一篇博文中,我将展示如何设置专门为使用内容协商使用Spring的ContentNegotiatingViewResolver视图。

1、内容协商如何工作

通过HTTP发出请求时,可以通过设置Accept header来指定你想要的响应类型。Web浏览器有这个预设来请求HTML(包括其他东西)。事实上,如果你看的话,你会发现浏览器实际上会发送非常混乱的Accept标头,这使得依赖它们变得不切实际。请参阅http://www.gethifi.com/blog/browser-rest-http-accept-headers的一个很好的讨论这个问题。Accept-header是混乱的,你也不能正常地改变它们(除非你使用JavaScript和AJAX)。

因此,针对Accept-header这种不是令人满意的情况,Spring提供了一些可使用的约定。(这是Spring 3.2的一个很好的变化,它使一个灵活的内容选择策略在所有Spring MVC中都可以使用,而不仅仅是在使用视图的时候)。你可以集中地配置一次内容协商策略,并在需要确定不同格式(媒体类型)的地方应用它。

2、Spring MVC 如何使用内容协商

Spring支持两种约定来选择所需格式:URL后缀或者URL参数。这些工作与使用Accept header一起工作。因此,可以以三种方式请求内容类型。默认情况下,按以下顺序进行检查:

  • 在URL中添加路径扩展(后缀)。所以,如果传入的URL是http://myserver/myapp/accounts/list.html, HTML是必需的。对于一个电子表格应该是http://myserver/myapp/accounts/list.xls的URL。媒体类型映射的后缀是通过JavaBeans激活框架或JAF(即激活,自动定义的。jar必须在类路径上)。
  • 一个URL参数如下:http://myserver/myapp/accounts/list?format=xls。参数的名称是默认格式,但这可能会被更改。默认情况下,使用参数是禁用的,但是在启用时,它会被检查。
  • 最后,检查Accept HTTP报头属性。这就是HTTP实际上是如何定义的,但是,正如前面提到的,使用它是有问题的。

设置这个的Java配置,看起来是这样的。只需通过它的配置器定制预定义的内容协商管理器。注意,MediaType助手类为大多数著名的media-types提供了预定义的常量。

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter { /**
* Setup a simple strategy: use all the defaults and return XML by default when not sure.
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.APPLICATION_XML);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

当使用XML配置时,内容协商策略是最容易通过ContentNegotiationManagerFactoryBean设置:

   <!--
Setup a simple strategy:
1. Take all the defaults.
2. Return XML by default when not sure.
-->
<bean id="contentNegotiationManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="defaultContentType" value="application/xml" />
</bean> <!-- Make this available across all of Spring MVC -->
<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

ContentNegotiationManage通过设置一个实现了ContentNegotationStrategy来实现上面提到的PPA策略 (path extension, then parameter, then Accept header)。

3、额外的配置选项

在Java配置中,可以使用配置器上的方法完全定制策略:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter { /**
* Total customization - see below for explanation.
*/
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(false).
favorParameter(true).
parameterName("mediaType").
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.APPLICATION_JSON).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

在XML中,可以使用工厂bean上的方法来配置策略:

 <!-- Total customization - see below for explanation. -->
<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="useJaf" value="false"/>
<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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

我们在这两种情况下都做了什么:

  • 禁用路径扩展。注意,偏爱并不意味着使用一种方法来选择另一种方法,它只是启用或禁用它。检查的顺序总是路径扩展、参数、Accept标头。
  • 启用URL参数的使用,而不是使用默认的参数,格式,我们将使用mediaType。
  • 完全忽略Accept标头。如果您的大多数客户实际上都是web浏览器(通常通过AJAX进行REST调用),那么这通常是最好的方法。
  • 不要使用JAF,而是手动地指定媒体类型映射——我们只希望支持JSON和XML。

4、用户帐户列表的例子

为了演示,我将一个简单的帐户清单应用程序作为我们的工作示例—屏幕截图显示了HTML中一个典型的帐户列表。完整的代码可以在Github上找到。

要返回JSON或XML的帐户列表,我需要这样的控制器。现在,我们将忽略HTML生成方法。

@Controller
class AccountController {
@RequestMapping(value="/accounts", method=RequestMethod.GET)
@ResponseStatus(HttpStatus.OK)
public @ResponseBody List<Account> list(Model model, Principal principal) {
return accountManager.getAccounts(principal) );
} // Other methods ...
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

下面是内容协商策略的设置:

    <!-- Simple strategy: only path extension is taken into account -->
<bean id="cnManager"
class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
<property name="favorPathExtension" value="true"/>
<property name="ignoreAcceptHeader" value="true" />
<property name="defaultContentType" value="text/html" />
<property name="useJaf" value="false"/> <property name="mediaTypes">
<map>
<entry key="html" value="text/html" />
<entry key="json" value="application/json" />
<entry key="xml" value="application/xml" />
</map>
</property>
</bean>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

或者,使用Java配置,代码看起来如下:

    @Override
public void configureContentNegotiation(
ContentNegotiationConfigurer configurer) {
// Simple strategy: only path extension is taken into account
configurer.favorPathExtension(true).
ignoreAcceptHeader(true).
useJaf(false).
defaultContentType(MediaType.TEXT_HTML).
mediaType("html", MediaType.TEXT_HTML).
mediaType("xml", MediaType.APPLICATION_XML).
mediaType("json", MediaType.APPLICATION_JSON);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

如果我在类路径上有JAXB2和Jackson,Spring MVC将自动设置必要的httpmessageconverter。我的domain class还必须使用JAXB2和Jackson注释来进行转换(否则消息转换器不知道该做什么)。为了响应注释(下面),注释的Account类如下所示。

/**
* Represents an account for a member of a financial institution. An account has
* zero or more {@link Transaction}s and belongs to a {@link Customer}. An aggregate entity.
*/
@Entity
@Table(name = "T_ACCOUNT")
@XmlRootElement
public class Account { // data-members omitted ... public Account(Customer owner, String number, String type) {
this.owner = owner;
this.number = number;
this.type = type;
} /**
* Returns the number used to uniquely identify this account.
*/
@XmlAttribute
public String getNumber() {
return number;
} /**
* Get the account type.
*
* @return One of "CREDIT", "SAVINGS", "CHECK".
*/
@XmlAttribute
public String getType() {
return type;
} /**
* Get the credit-card, if any, associated with this account.
*
* @return The credit-card number or null if there isn't one.
*/
@XmlAttribute
public String getCreditCardNumber() {
return StringUtils.hasText(creditCardNumber) ? creditCardNumber : null;
} /**
* Get the balance of this account in local currency.
*
* @return Current account balance.
*/
@XmlAttribute
public MonetaryAmount getBalance() {
return balance;
} /**
* Returns a single account transaction. Callers should not attempt to hold
* on or modify the returned object. This method should only be used
* transitively; for example, called to facilitate reporting or testing.
*
* @param name
* the name of the transaction account e.g "Fred Smith"
* @return the beneficiary object
*/
@XmlElement // Make these a nested <transactions> element
public Set<Transaction> getTransactions() {
return transactions;
} // Setters and other methods ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

下面是我们的帐户应用程序的JSON输出(请注意URL中的路径扩展)。

系统如何知道要转换为XML还是JSON?因为内容协商(PPA策略)的任何一个选项将使用上面讨论取决于ContentNegotiationManager是如何配置的。在本例中,URL以帐户结束。json因为路径扩展是唯一启用的策略。

在示例代码中,您可以通过在web.XML中设置一个活动概要文件,在XML或Java配置之间切换。这些概要文件分别是“xml”和“javaconfig”。

5、组合数据和表示格式

Spring MVC的REST支持构建在现有的MVC控制器框架之上。因此,可以使用相同的web应用程序返回信息,既包括原始数据(如JSON),也可以使用表示格式(如HTML)。

这两种技术都可以很容易地在同一个控制器中同时使用,就像这样:

@Controller
class AccountController {
// RESTful method
@RequestMapping(value="/accounts", produces={"application/xml", "application/json"})
@ResponseStatus(HttpStatus.OK)
public @ResponseBody List<Account> listWithMarshalling(Principal principal) {
return accountManager.getAccounts(principal);
} // View-based method
@RequestMapping("/accounts")
public String listWithView(Model model, Principal principal) {
// Call RESTful method to avoid repeating account lookup logic
model.addAttribute( listWithMarshalling(principal) ); // Return the view to use for rendering the response
return ¨accounts/list¨;
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

这里有一个简单的模式:@responsebody方法处理所有数据访问和与底层服务层(AccountManager)的集成。第二个方法调用第一个方法,并在模型中设置响应,以供视图使用。这就避免了重复的逻辑。

为了确定要选择的两个@requestmapping方法中的哪一个,我们再次使用了我们的PPA内容协商策略。它允许生产选项工作。url以账户。xml或账户。json映射到第一个方法,任何在帐户中结束的url。任何东西都映射到第二个。

6、另一种方法

或者,如果我们使用视图来生成所有可能的内容类型,那么我们可以只使用一个方法来完成整个事情。这是ContentNegotiatingViewResolver的由来,那将是我的下一篇文章的主题。

文章地址:content-negotiation-using-spring-mvc

Spring MVC Content Negotiation 转载的更多相关文章

  1. spring mvc入门教程 转载自【http://elf8848.iteye.com/blog/875830】

    目录  一.前言二.spring mvc 核心类与接口三.spring mvc 核心流程图 四.spring mvc DispatcherServlet说明 五.spring mvc 父子上下文的说明 ...

  2. spring mvc项目【转载】

    用了好几年的ssh2.最近打算研究下spring的mvc,看看如何,可以的话后期的项目将都是用springmvc+spring jdbc的形式,这样就少了其他框架的继承.由于以前没用过springmv ...

  3. 【Java Web开发学习】Spring MVC 开始配置

    Spring MVC 开始配置 转载:http://www.cnblogs.com/yangchongxing/p/8871370.htm 学习搭建最简单的Spring MVC框架. ======== ...

  4. Content Negotiation using Spring MVC

    There are two ways to generate output using Spring MVC: You can use the RESTful @ResponseBody approa ...

  5. 转载:深入理解Spring MVC 思想

    原文作者:赵磊 原文地址:http://elf8848.iteye.com/blog/875830 目录  一.前言二.spring mvc 核心类与接口三.spring mvc 核心流程图 四.sp ...

  6. 转载:如何让spring mvc web应用启动时就执行

    转载:如何让spring mvc web应用启动时就执行特定处理 http://www.cnblogs.com/yjmyzz/p/4747251.html# Spring-MVC的应用中 一.Appl ...

  7. 《转载》Spring MVC之@RequestBody, @ResponseBody 详解

    引言: 接上一篇文章讲述处理@RequestMapping的方法参数绑定之后,详细介绍下@RequestBody.@ResponseBody的具体用法和使用时机: 简介: @RequestBody 作 ...

  8. springboot Serving Web Content with Spring MVC

    Serving Web Content with Spring MVC This guide walks you through the process of creating a "hel ...

  9. 【转载】Spring MVC 整合 Freemarker

    前言 1.为什么要使用Spring MVC呢? 2.为什么要使用Freemarker呢? 3.为什么不使用Struts2呢? 此示例出现的原因就是发现了struts2的性能太差,所以学习Spring ...

随机推荐

  1. C#弹出窗体、C#导出Excel、C#数据展示框、C#弹出框

    1.new 一个窗体,然后设置窗体属性. 2.添加数据展示控件,显示数据. 3.添加按钮控件,调用导出Excel代码,实现导出Excel功能. using System; using System.C ...

  2. 基于Docker的TensorFlow机器学习框架搭建和实例源码解读

    概述:基于Docker的TensorFlow机器学习框架搭建和实例源码解读,TensorFlow作为最火热的机器学习框架之一,Docker是的容器,可以很好的结合起来,为机器学习或者科研人员提供便捷的 ...

  3. DAY3(PYTHON)

    一.or 和and的区别 X OR Y,如果X非0,则为X X OR Y,如果X为真,则为Y 二.continue 跳出当次循环 break 跳出循环 三.#输出1-2+3-4+5-6+......- ...

  4. [katalon] 页面切换

    UI自动化测试过程中会涉及到需要切换多个页面, 如点击一个按钮之后跳转到新的页面, 后者A站点提交信息后,B站点审核. Katalon虽然不支持控制多个浏览器,但是支持处理tab切换. 核心方法是使用 ...

  5. Linux下图形数据库Neo4j单机安装

    Neo4j数据库简介 Neo4j 是一个NoSQL的图形数据库(Graph Database).Neo4j使用图(graph)相关的概念来描述数据模型,把数据保存为图中的节点以及节点之间的关系.很多应 ...

  6. jar、war、ear

    附加jboss里面的application.xml <?xml version=”1.0″ encoding=”UTF-8″?> <application xmlns="h ...

  7. Oracle EBS when-validate-record 个性化无效果

    在对FORM进行个性化时,针对对应块添加 when-validate-record ,结果做实验的时候无效果. 原因: FORM 中对应的 BLOCK 没有 when-validate-record ...

  8. c/c++ 图的最短路径 Dijkstra(迪杰斯特拉)算法

    c/c++ 图的最短路径 Dijkstra(迪杰斯特拉)算法 图的最短路径的概念: 一位旅客要从城市A到城市B,他希望选择一条途中中转次数最少的路线.假设途中每一站都需要换车,则这个问题反映到图上就是 ...

  9. powershell脚本之windows服务与进程

    powershell脚本之windows服务与进程 服务与进程的区别: Windows服务是指系统自动完成的,不需要和用户交互的过程,可长时间运行的可执行应用程序 进程是程序运行的实例,系统会给运行中 ...

  10. [Hive_add_10] Hive 的 serde (序列化 & 反序列化) 操作

    0. 说明 serde 是序列化和反序列化 serialize & deserialize 是将文件中的字段映射成 Hive 中的列 1. 验证 1.1 openCSVSerde CSV 为逗 ...