1.REST的基础知识
当谈论REST时,有一种常见的错误就是将其视为“基于URL的Web服务”——将REST作为另一种类型的远程过程调用(remote procedurecall,RPC)机制,就像SOAP一样,只不过是通过简单的HTTP URL来触发,而不是使用SOAP大量的XML命名空间。恰好相反,REST与RPC几乎没有任何关系。RPC是面向服务的,并关注于行为和动作;而REST是面向资源的,强调描述应用程序的事物和名词。为了理解REST是什么,我们将它的首字母缩写拆分为不同的构成部分:
表述性(Representational):REST资源实际上可以用各种形式来进行表述,包括XML、JSON(JavaScript Object Notation)甚至HTML——最适合资源使用者的任意形式;
状态(State):当使用REST的时候,我们更关注资源的状态而不是对资源采取的行为;
转移(Transfer):REST涉及到转移资源数据,它以某种表述性形式从一个应用转移到另一个应用。更简洁地讲,REST就是将资源的状态以最适合客户端或服务端的形式从服务器端转移到客户端(或者反过来)。在REST中,资源通过URL进行识别和定位。至于RESTful URL的结构并没有严格的规则,但是URL应该能够识别资源,而不是简单的发一条命令到服务器上。再次强调,关注的核心是事物,而不是行为
REST中会有行为,它们是通过HTTP方法来定义的。具体来讲,也就是GET、POST、PUT、DELETE、PATCH以及其他的HTTP方法构成了
REST中的动作。这些HTTP方法通常会匹配为如下的CRUD动作:
Create:POSTRead:GET
Update:PUT或PATCH
Delete:DELETE
尽管通常来讲,HTTP方法会映射为CRUD动作,但这并不是严格的限制。有时候,PUT可以用来创建新资源,POST可以用来更新资源。实际上,POST请求非幂等性(non-idempotent)的特点使其成为一个非常灵活的方法,对于无法适应其他HTTP方法语义的操作,它都能够胜任。
2.Spring是如何支持REST的
本中,Spring支持以下方式来创建REST资源:
持PATCH方法;
借助RestTemplate,Spring应用能够方便地使用REST资源。
3.创建第一个REST端点
学习Spring REST编程模型的细节,我们将会不断构建这个控制器
编排和解排(marshaling/demarshaling)
表述形式:
制器所返回的对象转换为呈现给客户端的表述形式。
协商资源表述
你可以回忆一下在第5章中(以及图5.1所示),当控制器的处理方法完成时,通常会返回一个逻辑视图名。如果方法不直接返回逻辑视图名(例如方法返回void),那么逻辑视图名会根据请求的URL判断得出。DispatcherServlet接下来会将视图的名字传递给一个视图解析器,要求它来帮助确定应该用哪个视图来渲染请求结果。在面向人类访问的Web应用程序中,选择的视图通常来讲都会渲染为HTML。视图解析方案是个简单的一维活动。如果根据视图名匹配上了视图,那这就是我们要用的视图了。当要将视图名解析为能够产生资源表述的视图时,我们就有另外一个维度需要考虑了。视图不仅要匹配视图名,而且所选择的视图要适合客户端。如果客户端想要JSON,那么渲染HTML的视图就不行了——尽管视图名可能匹配。
Spring的ContentNegotiatingViewResolver是一个特殊的视图解析器,它考虑到了客户端所需要的内容类型。按照其最简单的形式ContentNegotiatingViewResolver可以按照下述形式进行配置:
容协商的两个步骤
1.确定请求的媒体类型;
2.找到适合请求媒体类型的最佳视图。
确定请求的媒体类型
在内容协商两步骤中,第一步是确定客户端想要什么类型的内容表述。表面上看,这似乎是一个很简单的事情。难道请求的Accept头部信息不是已经很清楚地表明要发送什么样的表述给客户端吗?遗憾的是,Accept头部信息并不总是可靠的。如果客户端是Web浏览器,那并不能保证客户端需要的类型就是浏览器在Accept头部所发送的值。Web浏览器一般只接受对人类用户友好的内容类型(如text/html),所以没有办法(除了面向开发人员的浏览器插件)指定不同的内容类型。
ContentNegotiatingViewResolver将会考虑到Accept头部信息并使用它所请求的媒体类型,但是它会首先查看URL的文件扩展名。如果URL在结尾处有文件扩展名的话,ContentNegotiatingViewResolver将会基于该扩展名确定所需的类型。如果扩展名是“.json”的话,那么所需的内容类型必须是“application/json”。如果扩展名是“.xml”,那么客户端请求的就是“application/xml”。当然,“.html”扩展名表明客户端所需的资源表述为HTML(text/html)。如果根据文件扩展名不能得到任何媒体类型的话,那就会考虑请求中的Accept头部信息。在这种情况下,Accept头部信息中的值就表明了客户端想要的MIME类型,没有必要再去查找了。最后,如果没有Accept头部信息,并且扩展名也无法提供帮助的话,ContentNegotiatingViewResolver将会使用“/”作为默认的内容类型,这就意味着客户端必须要接收服务器发送的任何形式的表述。一旦内容类型确定之后,ContentNegotiatingViewResolver就该将逻辑视图名解析为渲染模型的View。
与Spring的其他视图解析器不同,ContentNegotiatingViewResolver本身不会解析视图。而是委托给其他的视图解析器,让它们来解析视图。ContentNegotiatingViewResolver要求其他的视图解析器将逻辑视图名解析为视图。解析得到的每个视图都会放到一个列表中。这个列表装配完成后,ContentNegotiatingViewResolver会循环客户端请求的所有媒体类型,在候选的视图中查找能够产生对应内容类型的视图。第一个匹配的视图会用来渲染模型。
影响媒体类型的选择
到的事情如下所示:
会使用默认值;
2.通过请求参数指定内容类型;
将JAF(Java Activation Framework)作为根据扩展名查找媒体类
型的备用方案
有三种配置ContentNegotiationManager的方法:
直接声明一个ContentNegotiationManager类型的bean;
bean;
configureContentNegotiation()方法。
ContentNegotiationManager更加简单。
能希望在XML中配置ContentNegotiationManager使
用“application/json”作为默认的内容类型:
contentNegotiationManager属性中。
了默认的内容类型:
认的内容类型设置为“application/json”。
渲染为JSON输出。
除了程序清单16.2中的内容以外,还应该有一个能够处理HTML的视图解析器(如InternalResourceViewResolver或TilesViewResolver)。在大多数场景下,ContentNegotiatingViewResolver会假设客户端需要HTML,如ContentNegotiationManager配置所示。但是,如果客户端指定了它想要JSON(通过在请求路径上使用“.json”扩展名或Accept头部信息)的话,那么ContentNegotiatingViewResolver将会查找能够处理JSON视图的视图解析器。如果逻辑视图的名称为“spittles”,那么我们所配置的BeanNameViewResolver将会解析spittles()方法中所声明的View。这是因为bean名称匹配逻辑视图的名称。如果没有匹配的View的话ContentNegotiatingViewResolver将会采用默认的行为,将其输出为HTML。ContentNegotiatingViewResolver一旦能够确定客户端想要什么样的媒体类型,接下来就是查找渲染这种内容的视图
5.使用HTTP信息转换器
后所产生的资源表述。
产生XML响应。
RssChannelHttpMessageConverter会需要Rome库
方法是为控制器方法添加@ResponseBody注解。
在请求体中接收资源状态
到目前为止,我们只关注了REST端点如何为客户端提供资源。但是
REST并不是只读的,REST API也可以接受来自客户端的资源表述。
如果要让控制器将客户端发送的JSON和XML转换为它所使用的Java
对象,那是非常不方便的。在处理逻辑离开控制器的时候,Spring的
消息转换器能够将对象转换为表述——它们能不能在表述传入的时候
完成相同的任务呢?
@ResponseBody能够告诉Spring在把数据发送给客户端的时候,要
使用某一个消息器,与之类似,@RequestBody也能告诉Spring查找
一个消息转换器,将来自客户端的资源表述转换为对象。例如,假设
我们需要一种方式将客户端提交的新Spittle保存起来。我们可以
按照如下的方式编写控制器方法来处理这种请求
转换为JSON文档,并将其写入到响应体中。响应大致会如下所示
完成相同的任务呢?
按照如下的方式编写控制器方法来处理这种请求
处理请求。
为控制器默认设置消息转换
所定义的SpittleController可能就会如下所示:
部信息和状态码也能够为客户端提供响应的有用信息。接下来,我们
看一下在提供资源的时候,如何填充头部信息和设置状态码