赞
踩
REST就是一种架构思想,目的是建立长期的,不会随着技术发展被快速淘汰的Web服务架构,6个特征: 客户端服务器各司所职,服务器不存储状态,客户端可以使用缓存,接口统一(HTTP),系统分层,客户端可按需下载代码。 依靠以上6点可以很快理解为什么REST可以被认为是一种具有自主演化能力的架构。
REST中文翻译过来可以称为“表述性状态转移”,英文全称是“RepresentationalStateTransfer“。它依赖于一个无状态的、基于客户端 - 服务器的、可缓存的通信协议 ( 实际上,在几乎所有情况下,都是基于HTTP协议)。
REST是一种设计网络应用的开发模式。REST的想法是使用简单的HTTP用于机器之间的通话,而不是使用复杂的机制,如CORBA、RPC或SOAP用于机器之间的连接。
在许多方面来讲,万维网本身也是基于HTTP的,也可以被看作是一个基于REST的架构。
RESTful应用程序使用HTTP请求来操作数据(创建、更新)、读取数据(例如,查询)和删除数据。因此,REST可以使用HTTP执行四个CRUD(创建/读取/更新/删除)操作。
REST是RPC(远程过程调用)和Web服务(SOAP,WSDL等)的一个轻量级替代机制。在后面的内容中,我们将看到REST到底有多简单。
尽管简单,但REST的功能是齐全的;在Web服务中,基本上没有什么是RESTful架构不能做的。
REST不是一个“标准”,永远也不会有一个针对REST的W3C标准。同时呢,虽然也有与REST相关的编程框架,可以使你的工作变得很简单,但是,你还是可以推出自己的Perl,Java或C#等语言写的REST标准库
作为一种编程方法,REST是Web服务和远程过程调用RPC的一种轻量级替代品。
REST很像Web服务,它的一些特点包括:
平台无关性(你不必关心你的服务器是是否是Unix,不比关心客户端是否是一台Mac或其他任何东西);
语言无关性(你可以采用C#、Java、python等任何一种你熟悉的语言,不同语言之间的REST服务可以互相通信);
基于标准(在HTTP上运行);
可以很容易地在防火墙环境中试用。
如Web服务一样,REST并没有提供内置的安全功能、加密、会话管理、QoS保证等,但也和Web服务一样,这些都可以在构建HTTP的过程中增加:
出于安全方面的考虑,用户名/密码令牌经常被用到;
对于加密,REST可用于HTTPS(安全套接字)上。
...等等
在一个好的REST架构设计中,有一点不好的是不能使用cookie:在REST中,“ST”代表“状态转移”,事实上,在一个良好的REST设计中,操作是自包含的,这个意思是说:客户端发出的每个请求,必须包含服务器处理这个请求所需要的所有信息(REST中称为状态),服务器才可以处理该请求,而不依赖于前面的操作。
让我们用一个简单的Web服务作为例子:一个用于查询用户详细信息的电话簿查询应用程序。我们所拥有的资源就是用户的ID。使用Web服务和SOAP,发出的请求应该是这样的:
[html] view plain copy
1. <span style="font-size:16px;"><?xml version="1.0"?>
2. <soap:Envelope
3. xmlns:soap="http://www.w3.org/2001/12/soap-envelope"
4. soap:encodingStyle="http://www.w3.org/2001/12/soap-encoding">
5. <soap:body pb="http://www.acme.com/phonebook">
6. <pb:GetUserDetails>
7. <pb:UserID>12345</pb:UserID>
8. </pb:GetUserDetails>
9. </soap:Body>
10. </soap:Envelope></span>
(注意:不要关注细节,这只是一个例子)
上面的内容会通过HTTP的POST请求发送到服务器,而服务器返回的结果可能是一个XML文件,不过结果作为有效的数据内容,它会嵌入到一个SOAP请求封装中。
那么采用REST会是什么样呢?REST请求是这样的:
[cpp] view plain copy
1. <span style="font-size:16px;">http://www.acme.com/phonebook/UserDetails/12345</span>
注意看,REST请求并不是一个请求体,而只是一个url。此URL使用一个简单的HTTP GET请求发送到服务器,而HTTP回答就是结果的原始数据, 内部不会嵌入其他内容,就是你需要的、可以直接使用的数据。
从上面的例子很容易看出,为什么Web服务通常要使用库来创建SOAP / HTTP请求,并将其发送,然后再解析SOAP响应,因为有封装格式在其中;
而对于REST,你所需要的就是一个简单的网络连接,你甚至可以使用您的浏览器直接测试的你的API;
尽管如此,REST库(一些简化你操作的东西)还是存在的,我们将在以后讨论其中的一些。
注意URL的“方法”部分,为何不叫“GetUserDetails”,而是简单地“的UserDetails”?这是一个REST设计中常见的约定,使用名词而不是动词来表示简单的资源。
这一节的比喻
对于REST与SOAP的一个很好的比喻是寄一封信:用SOAP,你需要使用一个信封;而用REST,你只需要一张明信片。明信片是比较容易被接受人处理的,也减少纸张浪费(在REST中意味这消耗更少的带宽),此外就是有一个简短的内容。(当然,REST请求不限制长度,尤其是如果他们使用的是POST,而不是GET)
但是不携带太多的比喻来讲(不同于信件和明信片),REST和SOAP一样的安全。特别是,当REST使用安全套接字(HTTPS协议)时,你可以试用任何你认为合适的机制加密你的内容。如果没有加密机制,REST和SOAP都是不安全的;;而如果采用适当的加密,两者都同样安全
REST可以很容易地处理更复杂的请求,包括多个参数的情况。在更多的情况下,你在URL中只需要使用HTTP GET方法就足够了。看下一面的一个示例:
[cpp] view plain copy
1. <span style="font-size:16px;">http://www.acme.com/phonebook/UserDetails?firstName=John&lastName=Doe</span>
如果你需要传递更长的参数,或者是二进制的参数,你通常需要试用HTTP POST请求,并将参数包裹到POST内容里面。
有一条规则是这样的:GET请求应该用于处理只读型的请求,GET请求不应该改变服务器的状态和数据内容。而对于服务器中数据的创建、更新和删除操作,则使用POST请求。(当然,POST也可以用于只读型请求,正如在前面提到的,请求中包括更复杂变量的情况。
通常情况下,和其他大多数的网页是一样的,本网页可以看作是通过REST API接口提供服务:你使用GET请求读取数据内容,使用POST请求提交你的评论(此时,你需要更多更长的参数)。
虽然REST服务也可以在响应中使用XML作为数据组织的方式,但实际上,REST请求很少试用XML。正如我们前面提到的,在很多情况下,请求变量是简单的,我们没有必要增加额外的XML开销。
凡事总有例外,使用XML的一个优点是类型安全。然而,对于像REST这样的无状态系统,你应该总是验证你输入数据的合法性,包括XML格式的数据以及其他格式的数据。
在REST中,服务器的响应通常是XML格式的数据内容,示例如下:
[html] view plain copy
1. <span style="font-size:16px;"><parts-list>
2. <part id="3322">
3. <name>ACME Boomerang</name>
4. <desc>
5. Used by Coyote in <i>Zoom at the Top</i>, 1962
6. </desc>
7. <price currency="usd" quantity="1">17.32</price>
8. <uri>http://www.acme.com/parts/3322</uri>
9. </part>
10. <part id="783">
11. <name>ACME Dehydrated Boulders</name>
12. <desc>
13. Used by Coyote in <i>Scrambled Aches</i>, 1957
14. </desc>
15. <price currency="usd" quantity="pack">19.95</price>
16. <uri>http://www.acme.com/parts/783</uri>
17. </part>
18. </parts-list></span>
当然,其他格式的数据也是可以使用的,这和SOAP是不同的,REST并不局限于XML格式的数据,其他一些常用的数据格式内容包括CSV(更紧凑)以及JSON(对于JavaScript客户端以及其他语言解析很重要也很容易)。
有一种不适合做REST响应格式的特例是HTML,或任何其他对人类而言容易处理、而对客户端则不容易处理的数据格式。当然,凡事总有例外,具体的例外情况是,当REST服务返回一个人类可读的文件记录和作为一个RESTful应用程序访问整个万维网时,我们发现,HTML其实是最常见的REST响应格式...
这里有一个使用REST API的服务提供商的部分列表。请注意,其中一些还支持WSDL(Web服务)API,此外,让你可以选择使用哪一个,但在大多数情况下,当有两种选择时,REST调用更容易被创建,结果也更容易解析和使用,它也可以减少你的系统资源。
因此,事不宜迟,我们来看一些REST服务:
Twitter有一个REST API(事实上,这是他们原来的API,我可以告诉大家,到目前为止,它仍然是Twitter应用程序开发商所使用的主要API);
Flickr;
Amazon.com提供了REST服务,例如,为他们的S3存储解决方案;
Atom是一个对RSS的RESTful替代方案。
(这远不止是一个详尽的列表。)
下面是一个简单的例子:下面的URL发送一个REST请求到Twitter的搜索服务:http://search.twitter.com/search.atom?q=elkstein&count=5。这个特定的搜索请求是为了搜索字符串“elkstein”,用参数变量q来设置,且最多需要5个结果,使用count参数。当然,还有许多额外的参数,如Twitter的开发者页面记录等。
返回的响应是XML。一个略有不同的网址(点击这里)可用于获取JSON格式的结果。你会注意到,这两种格式很容易被机器解析(JSON是更容易在JavaScript中解析),你也会注意到,为了节省带宽和减少renpose大小,结果都没有保留缩进。事实上,所有可选的空白将被删除。因此,可能更容易在一个可解析XML或JSON数据的编辑器验证响应结果。
AJAX是一种流行的Web开发技术,使用JavaScript语言使得网页互动性更强。
在AJAX请求被发送到服务器时,使用的是XMLHttpRequest对象;根据响应的结果,客户端的JavaScript代码能动态地改变当前页面。
在许多方面,AJAX应用程序遵循REST设计原则。每个XMLHttpRequest请求可以被看作是一个REST服务请求,使用GET发送。而响应结果往往是JSON,而JSON恰恰是REST的一个流行的响应格式。(见REST服务器响应。)
使你的AJAX应用程序真正的RESTful化,就应该遵循标准的REST设计原则(稍后讨论)。你会发现,他们大多是一个好的设计,即使你不觉得你使用的是REST架构。
后面的一节提供了在JavaScript中发出的HTTP请求的代码样本,但如果你做过任何AJAX编程,你应该很熟悉这一切。
REST架构的关键组成部分:
1. 资源:由逻辑URLs来标识,代表资源的状态和功能
逻辑网址表示资源是可以由系统的其他部分全局寻址的;
资源是一个真正的RESTful设计的关键因素,就好象是RPC中“方法”或者是SOAP 网络服务中的“服务”。和RPC不同,你不是发出“getProductName”,然后再发出“getProductPrice“请求;在REST中,是将你要查看的产品数据作为一种资源 - 这种资源应该包含所有必需的信息(或与之相关的信息)。
2. 一个资源网:这意味着一个单一的资源不应该太大,也不应该包含过于详细的细节。每当有相关的内容,资源应包含更多信息的链接 - 就好像网页一样。
3. 该系统具有客户端 - 服务器:当然,一个组件的服务器也可以是另一个组件的客户端。
4. 没有任何连接状态:交互是无状态的(当然,服务器和资源可以是有状态的)。每个新的请求,应随身携带来完成该请求所需要的所有信息,必须不依赖于相同客户端以前的交互。
5. 资源在需要的情况下应该是可缓存:资源应该有到期日期/时间。该协议必须允许服务器明确指定哪些资源可以被缓存,可以缓存多长时间。
由于REST协议普遍使用HTTP,因此,HTTP的缓存控制头可以用做此目的;
客户必须尊重服务器对每个资源的的缓存规范。
6. 代理服务器:可以用来作为架构的一部分,以提高性能和可扩展性。可以使用任何标准的HTTP代理。
请注意,(作为一个客户端)你的应用程序可以使用REST服务,而不需要一个REST架构,例如,一个单一的机器,其中的非REST程序可以访问第三方REST服务。
设计一个REST架构的一些软件准则:
1. 不要使用“物理”的URLs:一些物理的URL指向具体的内容,例如一个XML文件“http://www.acme.com/inventory/product003.xml”。而逻辑URL并不会意味着一个物理文件,例如“http://www.acme.com/inventory/product/003”。
当然,即使有.xml扩展名,内容也是动态生成的。它应该是“人类易于识别的”、逻辑的urls,而不是物理urls。
2. 查询不应该返回一个超负荷数据:如果需要,可以提供分页机制。例如,“产品清单”GET请求应该返回第n个产品(例如,第10),同时带有下/上翻页的链接。
3. REST响应,即便可以是任何东西,也请确保它是有据可查的,确保他不会改变哪怕一点点输出格式(因为改变会对现有的客户端造成破坏)。
请记住,即使是人类可读的输出,但你的客户并不是人类用户;
如果输出是在XML中,确保您将它保存在schema模式或DTD文件中。
4. 不要让客户端构建额外的动作的URL,包括REST响应的实际url。例如,对“产品清单”的请求,可以返回每个产品的ID,规范说,你应该使用http://www.acme.com/product/PRODUCT_ID得到更多的细节,这是一种很糟糕的设计;相反,好的设计中,响应应包括每个产品的实际url地址:http://www.acme.com/product/001263等。
按照这种设计思路,意味着输出会较大。但是,这也意味着您可以轻松地直接对新得到的URL进行按需操作,而不需要改变客户端代码。
5. GET访问请求,不应该引起服务器任何的状态改变。任何服务器状态的改变,都应该是一个POST请求(或其他HTTP动词,如DELETE)造成的。
ROA(REST Oriented Architecture,面向REST的架构),是一个使用REST服务的SOA(Service Oriented Architecture ,面向服务的架构)的花哨的名称。
基于SOAP的SOA和ROA相比,最主要的优势是更成熟的工具支持;然而,随时间的发展,这一切都会改变;另一个SOA的优点是因为XML请求的类型安全原因,(当然,如果开发商希望,ROA的响应也可以使用XML)。
ROA的主要优点是便于实施、设计灵活、轻量级的对象访问方式。在某种程度上,SOA和SOAP就好像是穿着西装的人,你会发现在银行和金融业都是这样的人。相反,有人需要快速可上手的东西并且要具有良好的性能和低开销,这时候最好使用REST和ROA。
例如,当解释为什么他们选择REST而不是SOAP时,雅虎的人说,他们“相信REST具有较低的进入壁垒,比SOAP更容易使用,且完全足够[雅虎]服务”(雅虎开发人员网络常见问题,如2008年2月)。这不仅适用于REST与SOAP之间,也适用于ROA与SOA之间。
REST的另一个优点在于性能:对缓存更好地支持、轻量级的请求和响应、更容易响应解析,REST允许灵活的客户端和服务器,也降低了网络流量。
随着REST的成熟,它可能会被更好地理解、更受到大家欢迎,即使在较为保守的行业中。
附上一些对本章的评论:
这是一个很好的文章。但与所应有的尊重,我不同意你的第一条语句,ROA是具有REST服务的SOA。
ROA和SOA代表了2种不同的设计风格。在SOA中“服务”或“动作“是重要的,它是建立在这些动词(服务是设计和组织)的基础上。
ROA或面向资源的架构中,其重要性是考虑到资源。在这种情况下,关注的是实体或“名词”的重要性,重点是组织服务中的资源。
REST是一种建筑风格或一种设计模式,它可以和SOAP结合来使用。我们不能说基于SOAP的SOA或者SOA是对我们采用任何相关技术(不仅仅是SOAP)的一种架构或蓝图。
WSDL是一个W3C标准,是一种Web服务描述语言,它通常被用来描述SOAP服务器提供的服务的详情。虽然WSDL具有灵活的服务绑定选项(例如,服务可以通过SMTP邮件服务器提供),但是它原先并没有支持GET和POST等 HTTP操作,而由于REST服务经常使用HTTP动词(如PUT和DELETE),因此WSDL不是一个记录REST服务的好选择。
在2.0中,WSDL支持几乎所有的HTTP动词,因此它现在被认为是一个记录REST服务的可接受的方法。
第二个选择是WADL,中文名称是“Web应用描述语言”。 WADL的是由Sun Microsystems公司倡导的。像REST一样,WADL的也是轻量级的,比WSDL容易理解和容易编写。在某些方面,它不像WSDL灵活(不绑定SMTP服务器),但它对任何REST服务都足够了,而且动词也少得多。
这里是一个WADL规范的片段,描述了亚马逊的“项目搜索”服务:
[html] view plain copy
1. <span style="font-size:16px;"><method name="GET" id="ItemSearch">
2. <request>
3. <param name="Service" style="query"
4. fixed="AWSECommerceService"/>
5. <param name="Version" style="query" fixed="2005-07-26"/>
6. <param name="Operation" style="query" fixed="ItemSearch"/>
7. <param name="SubscriptionId" style="query"
8. type="xsd:string" required="true"/>
9. <param name="SearchIndex" style="query"
10. type="aws:SearchIndexType" required="true">
11. <option value="Books"/>
12. <option value="DVD"/>
13. <option value="Music"/>
14. </param>
15. <param name="Keywords" style="query"
16. type="aws:KeywordList" required="true"/>
17. <param name="ResponseGroup" style="query"
18. type="aws:ResponseGroupType" repeating="true">
19. <option value="Small"/>
20. <option value="Medium"/>
21. <option value="Large"/>
22. <option value="Images"/>
23. </param>
24. </request>
25. <response>
26. <representation mediaType="text/xml"
27. element="aws:ItemSearchResponse"/>
28. </response>
29. </method></span>
正如你可以看到,大多是不言自明的格式,它通过使用XML schema这种类型类型安全的好东西丰富了REST。
整个文件大约只有10行,要长于这个片段(包括XML命名空间规格,导入架构语法等),而且从其中可以发现WADL规范。
另一个现实世界的WADL文件,可以查看CSS验证服务规范的W3C独角兽项目。
然而,一些REST的倡导者,认为即使在轻量级的WADL也有点多余。而事实上,大多数REST服务的记录并不比文字描述(人类可读的HTML文件)多。
本节提供简单的例子,是使用Python来访问REST服务。但更重要的是,使用python语言发送HTTP GET和/或POST请求的机制;而使用REST的其余部分就只是一个简单的架构设计问题。
发送一个GET请求:
python中的urllib2模块使得读取urls变得很简单:
[python] view plain copy
1. import urllib2
2.
3. url = 'http://www.acme.com/products/3322'
4. re<span id="result_box" class="long_text" lang="zh-CN"><span></span></span>sponse = urllib2.urlopen(url).read()
错误报告作为例外(urllib2.HTTPError或urllib2.URLError)。
发送一个POST请求:
POST请求也很简单,只需要将加密的请求数据作为附加参数传递给urlopen函数就可以了:
[python] view plain copy
1. <span style="font-size:16px;">import urllib
2. import urllib2
3.
4. url = 'http://www.acme.com/users/details'
5. params = urllib.urlencode({
6. 'firstName': 'John',
7. 'lastName': 'Doe'
8. })
9. response = urllib2.urlopen(url, params).read()</span>
注意加密是通过urllib2中的一个功能函数来完成的。
,还很不完善先记录下来。
部署REST服务:web service project, 选择了REST的web service
1 package com.test;
2
3 import javax.ws.rs.Consumes;
4 import javax.ws.rs.GET;
5 import javax.ws.rs.POST;
6 import javax.ws.rs.Path;
7 import javax.ws.rs.PathParam;
8 import javax.ws.rs.Produces;
9 import com.sun.jersey.spi.resource.Singleton;
10
11 @Produces("text/plain")
12 @Path("customers")
13 @Singleton
14 public class Interface {
15
16 @GET
17 public String getCustomers(){
18 return "getCustomers all";
19 }
20 @GET
21 @Path("{id}")
22 public String getCustomer(@PathParam("id") String uid) {
23 return "your id is "+ uid;
24 }
25 }
客户端调用:java project
1 package com.app;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStreamReader;
6 import java.net.HttpURLConnection;
7 import java.net.MalformedURLException;
8 import java.net.URL;
9
10 public class app {
11
12 /**
13 * @param args
14 */
15 public static void main(String[] args) throws MalformedURLException {
16 // TODO Auto-generated method stub
17 //实例一个URL资源
18 URL url = null;
19 try {
20 url = new URL("http://localhost:8080/java_ws01/services/customers");
21 //url = new URL("http://localhost:8080/java_ws01/services/customers/321");
22 HttpURLConnection connet;
23 connet = (HttpURLConnection) url.openConnection();
24 if(connet.getResponseCode() != 200){
25 throw new IOException(connet.getResponseMessage());
26 }
27 //将返回的值存入到String中
28 BufferedReader brd = new BufferedReader(new InputStreamReader(connet.getInputStream()));
29
30 System.out.println(brd.readLine());
31
32 connet.disconnect();
33 } catch (IOException e) {
34 // TODO Auto-generated catch block
35 e.printStackTrace();
36 }
37 }
38 }
基于 REST 的 Web 服务遵循一些基本的设计原则:
· 系统中的每一个对象或是资源都可以通过一个唯一的 URI 来进行寻址,URI 的结构应该简单、可预测且易于理解,比如定义目录结构式的 URI。
· 以遵循 RFC-2616 所定义的协议的方式显式地使用 HTTP 方法,建立创建、检索、更新和删除(CRUD:Create, Retrieve, Update and Delete)操作与 HTTP 方法之间的一对一映射:
o 若要在服务器上创建资源,应该使用 POST 方法;
o 若要检索某个资源,应该使用 GET 方法;
o 若要更改资源状态或对其进行更新,应该使用 PUT 方法;
o 若要删除某个资源,应该使用 DELETE 方法。
· URI 所访问的每个资源都可以使用不同的形式加以表示(比如 XML 或者 JSON),具体的表现形式取决于访问资源的客户端,客户端与服务提供者使用一种内容协商的机制(请求头与 MIME 类型)来选择合适的数据格式,最小化彼此之间的数据耦合。
JAX-RS -- Java API for RESTful Web Services
Java EE 6 引入了对 JSR-311 的支持。JSR-311(JAX-RS:Java API for RESTful Web Services)旨在定义一个统一的规范,使得 Java 程序员可以使用一套固定的接口来开发 REST 应用,避免了依赖于第三方框架。同时,JAX-RS 使用 POJO 编程模型和基于标注的配置,并集成了 JAXB,从而可以有效缩短 REST 应用的开发周期。
JAX-RS 定义的 API 位于 javax.ws.rs 包中,其中一些主要的接口、标注和抽象类如 图 1 所示。
JAX-RS 的具体实现由第三方提供,例如 Sun 的参考实现 Jersey、Apache 的 CXF 以及 JBoss 的 RESTEasy。
在接下来的文章中,将结合一个记账簿应用向读者介绍 JAX-RS 一些关键的细节。
记账簿示例应用程序中包含了 3 种资源:账目、用户以及账目种类,用户与账目、账目种类与账目之间都是一对多的关系。记账簿实现的主要功能包括:
1. 记录某用户在什么时间花费了多少金额在哪个种类上
2. 按照用户、账目种类、时间或者金额查询记录
3. 对用户以及账目种类的管理
Web 资源作为一个 Resource 类来实现,对资源的请求由 Resource 方法来处理。Resource 类或 Resource 方法被打上了 Path 标注,Path 标注的值是一个相对的 URI 路径,用于对资源进行定位,路径中可以包含任意的正则表达式以匹配资源。和大多数 JAX-RS 标注一样,Path 标注是可继承的,子类或实现类可以继承超类或接口中的 Path 标注。
Resource 类是 POJO,使用 JAX-RS 标注来实现相应的 Web 资源。Resource 类分为根 Resource 类和子 Resource 类,区别在于子 Resource 类没有打在类上的 Path 标注。Resource 类的实例方法打上了 Path 标注,则为 Resource 方法或子 Resource 定位器,区别在于子 Resource 定位器上没有任何 @GET、@POST、@PUT、@DELETE 或者自定义的 @HttpMethod。清单 1 展示了示例应用中使用的根 Resource 类及其 Resource 方法。
@Path("/") |
JAX-RS 中涉及 Resource 方法参数的标注包括:@PathParam、@MatrixParam、@QueryParam、@FormParam、@HeaderParam、@CookieParam、@DefaultValue 和 @Encoded。这其中最常用的是 @PathParam,它用于将 @Path 中的模板变量映射到方法参数,模板变量支持使用正则表达式,变量名与正则表达式之间用分号分隔。例如对 清单 1 中所示的 BookkeepingService 类,如果使用 Get 方法请求资源”/person/jeffyin”,则 readPersonByName 方法将被调用,方法参数 name 被赋值为”jeffyin”;而如果使用 Get 方法请求资源”/person/123”,则 readPerson 方法将被调用,方法参数 id 被赋值为 123。要了解如何使用其它的参数标注 , 请参考 JAX-RS API。
JAX-RS 规定 Resource 方法中只允许有一个参数没有打上任何的参数标注,该参数称为实体参数,用于映射请求体。例如 清单 1中所示的 BookkeepingService 类的 createPerson 方法和 updatePerson 方法的参数 person。
Resource 方法合法的参数类型包括:
1. 原生类型
2. 构造函数接收单个字符串参数或者包含接收单个字符串参数的静态方法 valueOf 的任意类型
3. List<T>,Set<T>,SortedSet<T>(T 为以上的 2 种类型)
4. 用于映射请求体的实体参数
Resource 方法合法的返回值类型包括:
1. void:状态码 204 和空响应体
2. Response:Response 的 status 属性指定了状态码,entity 属性映射为响应体
3. GenericEntity:GenericEntity 的 entity 属性映射为响应体,entity 属性为空则状态码为 204,非空则状态码为 200
4. 其它类型:返回的对象实例映射为响应体,实例为空则状态码为 204,非空则状态码为 200
对于错误处理,Resource 方法可以抛出非受控异常 WebApplicationException 或者返回包含了适当的错误码集合的 Response 对象。
通过 Context 标注,根 Resource 类的实例字段可以被注入如下类型的上下文资源:
1. Request、UriInfo、HttpHeaders、Providers、SecurityContext
2. HttpServletRequest、HttpServletResponse、ServletContext、ServletConfig
要了解如何使用第 1 种类型的上下文资源 , 请参考 JAX-RS API。
JAX-RS 定义了 @POST、@GET、@PUT 和 @DELETE,分别对应 4 种 HTTP 方法,用于对资源进行创建、检索、更新和删除的操作。
POST 标注用于在服务器上创建资源,如 清单 2 所示。
@Path("/") public class BookkeepingService { ...... @Path("/account/") @POST @Consumes("application/json") public Response createAccount(Account account) { ...... } ...... |
如果使用 POST 方法请求资源”/account”,则 createAccount 方法将被调用,JSON 格式的请求体被自动映射为实体参数 account。
GET 标注用于在服务器上检索资源,如 清单 3 所示。
@Path("/") public class BookkeepingService { ...... @Path("/person/{id}/accounts/") @GET @Produces("application/json") public Account[] readAccountsByPerson(@PathParam("id") int id) { ...... } ...... @Path("/accounts/{beginDate:\\d{4}-\\d{2}-\\d{2}},{endDate:\\d{4}-\\d{2}-\\d{2}}/") @GET @Produces("application/json") public Account[] readAccountsByDateBetween(@PathParam("beginDate") String beginDate, @PathParam("endDate") String endDate) throws ParseException { ...... } ...... |
如果使用 GET 方法请求资源”/person/123/accounts”,则 readAccountsByPerson 方法将被调用,方法参数 id 被赋值为 123,Account 数组类型的返回值被自动映射为 JSON 格式的响应体;而如果使用 GET 方法请求资源”/accounts/2008-01-01,2009-01-01”,则 readAccountsByDateBetween 方法将被调用,方法参数 beginDate 被赋值为”2008-01-01”,endDate 被赋值为”2009-01-01”,Account 数组类型的返回值被自动映射为 JSON 格式的响应体。
PUT 标注用于更新服务器上的资源,如 清单 4 所示。
@Path("/") public class BookkeepingService { ...... @Path("/account/") @PUT @Consumes("application/json") public Response updateAccount(Account account) { ...... } ...... |
如果使用 PUT 方法请求资源”/account”,则 updateAccount 方法将被调用,JSON 格式的请求体被自动映射为实体参数 account。
DELETE 标注用于删除服务器上的资源,如 清单 5 所示。
@Path("/") public class BookkeepingService { ...... @Path("/account/{id:\\d+}/") @DELETE public Response deleteAccount(@PathParam("id") int id) { ...... } ...... |
如果使用 DELETE 方法请求资源”/account/323”,则 deleteAccount 方法将被调用,方法参数 id 被赋值为 323。
Web 资源可以有不同的表现形式,服务端与客户端之间需要一种称为内容协商(Content Negotiation)的机制:作为服务端,Resource 方法的 Produces 标注用于指定响应体的数据格式(MIME 类型),Consumes 标注用于指定请求体的数据格式;作为客户端,Accept 请求头用于选择响应体的数据格式,Content-Type 请求头用于标识请求体的数据格式。
JAX-RS 依赖于 MessageBodyReader 和 MessageBodyWriter 的实现来自动完成返回值到响应体的序列化以及请求体到实体参数的反序列化工作,其中,XML 格式的请求/响应数据与 Java 对象的自动绑定依赖于 JAXB 的实现。
用户可以使用 Provider 标注来注册使用自定义的 MessageBodyProvider,如 清单 6 所示,GsonProvider 类使用了 Google Gson 作为 JSON 格式的 MessageBodyProvider 的实现。
@Provider @Produces("application/json") @Consumes("application/json") public class GsonProvider implements MessageBodyWriter<Object>, MessageBodyReader<Object> { private final Gson gson; public GsonProvider() { gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().setDateFormat( "yyyy-MM-dd").create(); } public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, String> httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { return gson.fromJson(new InputStreamReader(entityStream, "UTF-8"), type); } public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return true; } public long getSize(Object obj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) { return -1; } public void writeTo(Object obj, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { entityStream.write(gson.toJson(obj, type).getBytes("UTF-8")); } } |
由于 JAX-RS 和 JPA 同样都使用了基于 POJO 和标注的编程模型,因而很易于结合在一起使用。示例应用中的 Web 资源 ( 如账目 ) 同时也是持久化到数据库中的实体,同一个 POJO 类上既有 JAXB 的标注,也有 JPA 的标注 ( 或者还有 Gson 的标注 ) ,这使得应用中类的个数得以减少。如 清单 7 所示,Account 类可以在 JAX-RS 与 JPA 之间得到复用,它不但可以被 JAX-RS 绑定为请求体 / 响应体的 XML/JSON 数据,也可以被 JPA 持久化到关系型数据库中。
@Entity @Table(name = "TABLE_ACCOUNT") @XmlRootElement public class Account { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "COL_ID") @Expose private int id; @ManyToOne @JoinColumn(name = "COL_PERSON") @Expose private Person person; @Column(name = "COL_AMOUNT") @Expose private BigDecimal amount; @Column(name = "COL_DATE") @Expose private Date date; @ManyToOne @JoinColumn(name = "COL_CATEGORY") @Expose private Category category; @Column(name = "COL_COMMENT") @Expose private String comment; ...... |
REST 作为一种轻量级的 Web 服务架构被越来越多的开发者所采用,JAX-RS 的发布则规范了 REST 应用开发的接口。本文首先阐述了 REST 架构的基本设计原则,然后通过一个示例应用展示了 JAX-RS 是如何通过各种标注来实现以上的设计原则的,最后还介绍了 JAX-RS 与 JPA、Gson 的结合使用。本文的示例应用
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。