简介
RESTful:
REST (REpresentation State Transfer) 描述了一个架构样式的网络系统,比如 web 应用程序。它首次出现在 2000 年 Roy Fielding 的博士论文中,他是 HTTP 规范的主要编写者之一。REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。
Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个惟一的地址。所有资源都共享统一的界面,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和 DELETE。Hypermedia 是应用程序状态的引擎,资源表示通过超链接互联。
"Statelessness" 是restful风格最重要的原则。它的含义是指,客户端和服务器交互的过程中(各次请求之间)是无状态的。 无状态是针对于“状态”来说的。 一个WEB应用协议中的“状态”指的是,为两个相互关联的用户交互操作保留某种公共信息,例如用户登录信息,工作流等。 这些信息具有不同作用域,如page,request,session,application等。通常由服务器负责保存这些信息。
“无状态” 的概念逐渐流行,得益于分布式系统的发展。首先,无状态请求易于实现负载均衡。在分布式web系统下,有多个可用服务器,每个服务器都可以处理客户端请求。 传统的有状态请求,因为状态信息只保存在第一次发起请求的那台服务器上,之后的请求都只能由这台服务器来处理,服务器无法自由调度请求。无状态请求则完全没有这个限制。其次,无状态请求有较强的容错性和可伸缩性。如果一台服务器宕机,无状态请求可以透明地交由另一台可用服务器来处理,而有状态的请求则会因为存储请求状态信息的服务器宕机而承担状态丢失的风险。
需要注意的是,“状态"指请求的状态,而不是资源的状态。Restful风格的无状态约束要求服务器不保存请求状态,如果确实需要维持用户状态,也应由客户端负责。传递user credentials 是Restful的,而传递sessionID是unRestful的,因为session信息保存在服务器端。
Java 资源
JAX-RS 建立了一种特殊的语言来描述资源,正如由其编程模型所表示的。有五种主要条目:根资源、子资源、资源方法、子资源方法以及子资源定位器。
根资源
根资源是由 @Path
注释的 Java 类。@Path
注释提供了一个 value
属性,用来表明此资源所在的路径。value
属性可以是文本字符、变量或变量外加一个定制的正则表达式。清单 1 给出了一个例子。
清单 1. JAX-RS 根资源
package com.ibm.jaxrs.sample.organization; import javax.ws.rs.Path; @Path(value="/contacts") public class ContactsResource { ... } |
子资源
子资源是作为 subresource locator 调用的结果返回的 Java 类。它们类似于根资源,只不过它们不是由 @Path
注释的,因它们的路径是由子资源定位器给出的。子资源通常包含由 HTTP 请求方法指示符(designator)注释的方法以便服务此请求。如果它们不包含如此注释的方法,那么它们将会通过指派给合适的子资源定位器来进一步解析此资源处理请求。
清单 2. JAX-RS 子资源
package com.ibm.jaxrs.sample.organization; import javax.ws.rs.GET; public class Department { @GET public String getDepartmentName() { ... } } |
如上所示的清单 2 展示了由 ContactsResource.getContactDepartment
方法返回的子资源。在这个例子中,如果一个 HTTP GET 请求被发送给 /contact/{contactName}/department
路径,那么 Department
子资源内的 getDepartmentName
资源方法就会处理此请求。
资源方法
资源方法是根资源或子资源内绑定到 HTTP 方法的 Java 方法。绑定是通过诸如 @GET
这样的注释完成的。
清单 3. JAX-RS 资源方法
package com.ibm.jaxrs.sample.organization; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; @Path(value="/contacts") public class ContactsResource { @GET public List<ContactInfo> getContacts() { ... } } |
在清单 3 的例子中,发送到 /contacts
路径的 HTTP GET 请求将会由 getContacts()
资源方法处理。
子资源方法
子资源方法非常类似于资源方法;惟一的区别是子资源方法也是由 @Path
注释的,此注释进一步限定了该方法的选择。
清单 4. JAX-RS 子资源方法
package com.ibm.jaxrs.sample.organization; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; @Path(value="/contacts") public class ContactsResource { @GET public List<ContactInfo> getContacts() { ... } @GET @Path(value="/ids") public List<String> getContactIds() { ... } } |
在清单 4 中,发送到 /contacts/ids
路径的 HTTP GET 请求将会由 getContactIds()
子资源方法处理。
子资源定位器
子资源定位器是能进一步解析用来处理给定请求的资源的一些方法。它们非常类似于子资源方法,因它们具备一个 @Path
注释,但不具备 HTTP 请求方法指示符,比如 @GET
注释。
清单 5. JAX-RS 子资源定位器
package com.ibm.jaxrs.sample.organization; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @Path(value="/contacts") public class ContactsResource { @GET public List<ContactInfo> getContactss() { ... } @GET @Path(value="/ids") public List<String> getContactIds() { ... } @Path(value="/contact/{contactName}/department") public Department getContactDepartment(@PathParam(value="contactName") String contactName) { ... } } |
在上述例子中,对 /contact/{contactName}/department
路径的任何 HTTP 请求都将由 getContactDepartment
子资源定位器处理。 {contactName}
部分表明 contact
路径部分之后可以是任何合法的 URL 值。
注释
本节将会探讨一些重要的注释及其使用。对于由 JAX-RS 规范提供的注释的完整列表,可以参考本文的 参考资料 部分给出的 JSR-311 链接。
@Path
@Path
注释被用来描述根资源、子资源方法或子资源的位置。value
值可以包含文本字符、变量或具有定制正则表达式的变量。清单 6 的例子展示了 @Path
注释的主要应用。
清单 6. @Path 的使用
package com.ibm.jaxrs.sample.organization; import java.util.List; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; @Path(value="/contacts") public class ContactsResource { @GET @Path(value="/{emailAddress:.+@.+\\.[a-z]+}") public ContactInfo getByEmailAddress(@PathParam(value="emailAddress") String emailAddress) { ... } @GET @Path(value="/{lastName}") public ContactInfo getByLastName(@PathParam(value="lastName") String lastName) { ... } } |
ContactsResource
类上的注释表明对 /contacts
路径的所有请求都将由 ContactsResource
根资源处理。getByEmailAddress
上的 @Path
注释则表明任何发送到 /contacts/{emailAddress}
的请求(其中 emailAddress
代表的是正则表达式 .+@.+\\.[a-z]+
)都将由 getByEmailAddress
处理。
getByLastName
方法上的 @Path
注释指定了发送到 /contacts/{lastName}
路径的所有请求(其中 lastName
代表的是一个与getByEmailAddress
内的正则表达式不匹配的有效的 URL 部分)都将由 getByLastName
方法处理。
@GET、@POST、@PUT、@DELETE、@HEAD
@GET、@POST、@PUT、@DELETE 以及 @HEAD 均是 HTTP 请求方法指示符注释。您可以使用它们来绑定根资源或子资源内的 Java 方法与 HTTP 请求方法。HTTP GET 请求被映射到由 @GET 注释的方法;HTTP POST 请求被映射到由 @POST 注释的方法,以此类推。用户可能还需要通过使用 @HttpMethod
注释定义其自己的定制 HTTP 请求方法指示符。
清单 7. 定制的 HTTP 请求方法指示符注释
package com.ibm.jaxrs.sample.organization; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import javax.ws.rs.HttpMethod; @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) @HttpMethod("GET") public @interface CustomGET { } |
上述的声明定义了 @CustomGET
注释。此注释将具有与 @GET 注释相同的语义值并可用在其位置上。
@Conumes 和 @Produces
@Consumes
注释代表的是一个资源可以接受的 MIME 类型。@Produces
注释代表的是一个资源可以返回的 MIME 类型。这些注释均可在资源、资源方法、子资源方法、子资源定位器或子资源内找到。
清单 8. @Consumes/@Produces
package com.ibm.jaxrs.sample.organization; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; @Path(value="/contacts") public class ContactsResource { @GET @Path(value="/{emailAddress:.+@.+\\.[a-z]+}") @Produces(value={"text/xml", "application/json"}) public ContactInfo getByEmailAddress(@PathParam(value="emailAddress") String emailAddress) { ... } @GET @Path(value="/{lastName}") @Produces(value="text/xml") public ContactInfo getByLastName(@PathParam(value="lastName") String lastName) { ... } @POST @Consumes(value={"text/xml", "application/json"}) public void addContactInfo(ContactInfo contactInfo) { ... } } |
对于上述的 getByEmailAddress
和 addContactInfo
方法,它们均能处理 text/xml
和 application/json
。被接受或返回的资源表示将依赖于客户机设置的 HTTP 请求头。@Consumes
注释针对 Content-Type
请求头进行匹配,以决定方法是否能接受给定请求的内容。
在清单 9 中,application/json
的 Content-Type
头再加上对路径 /contacts
的 POST,表明我们的 ContactsResource
类内的 addContactInfo
方法将会被调用以处理请求。
清单 9. Content-Type 头部的使用
POST /contacts HTTP/1.1 Content-Type: application/json Content-Length: 32 |
相反地,@Produces
注释被针对 Accept
请求头进行匹配以决定客户机是否能够处理由给定方法返回的表示。
清单 10. Accept 头部的使用
GET /contacts/johndoe@us.ibm.com HTTP/1.1 Accept: application/json |
在清单 10 中,对 /contacts/johndoe@us.ibm.com 的 GET
请求表明了 getByEmailAddress
方法将会被调用并且返回的格式将会是application/json
,而非 text/xml。