1、网站即软件,网站开发,完全可以采用软件开发的模式。但是传统上,软件和网络是两个不同的领域,很少有交集;软件开发主要针对单机环境,网络则主要研究系统之间的通信。互联网的兴起,使得这两个领域开始融合,现在我们必须考虑,如何开发在互联网环境中使用的软件。RESTful架构,就是目前最流行的一种互联网软件架构。它结构清晰、符合标准、易于理解、扩展方便,所以正得到越来越多网站的采用。
2、如果一个架构符合REST原则,就称它为RESTful架构。要理解RESTful架构,最好的方法就是去理解Representational State Transfer这个词组到底是什么意思,它的每一个词代表了什么涵义。如果你把这个名称搞懂了,也就不难体会REST是一种什么样的设计。
下面我们对REST进行拆分讲解:
第一部分:资源(Resources)
REST的名称"表现层状态转化"中,省略了主语。"表现层"其实指的是"资源"(Resources)的"表现层"。
所谓"资源",就是网络上的一个实体,或者说是网络上的一个具体信息。它可以是一段文本、一张图片、一首歌曲、一种服务,总之就是一个具体的实体。你可以用一个URI(统一资源定位符)指向它,每种资源对应一个特定的URI。要获取这个资源,访问它的URI就可以,因此URI就成了每一个资源的地址或独一无二的识别符。
所谓"上网",就是与互联网上一系列的"资源"互动,调用它的URI。
第二部分:表现层(Representation)
"资源"是一种信息实体,它可以有多种外在表现形式。我们把"资源"具体呈现出来的形式,叫做它的"表现层"(Representation)。比如,文本可以用txt格式表现,也可以用HTML格式、XML格式、JSON格式表现,甚至可以采用二进制格式;图片可以用JPG格式表现,也可以用PNG格式表现。URI只代表资源的实体,不代表它的形式。严格地说,有些网址最后的".html"后缀名是不必要的,因为这个后缀名表示格式,属于"表现层"范畴,而URI应该只代表"资源"的位置。它的具体表现形式,应该在HTTP请求的头信息中用Accept和Content-Type字段指定,这两个字段才是对"表现层"的描述。
第三部分:状态转化(State Transfer)
访问一个网站,就代表了客户端和服务器的一个互动过程。在这个过程中,势必涉及到数据和状态的变化。互联网通信协议HTTP协议,是一个无状态协议。这意味着,所有的状态都保存在服务器端。因此,如果客户端想要操作服务器,必须通过某种手段,让服务器端发生"状态转化"(State Transfer)。而这种转化是建立在表现层之上的,所以就是"表现层状态转化"。客户端用到的手段,只能是HTTP协议。具体来说,就是HTTP协议里面,四个表示操作方式的动词:GET、POST、PUT、DELETE。它们分别对应四种基本操作:GET用来获取资源,POST用来新建资源(也可以用于更新资源),PUT用来更新资源,DELETE用来删除资源。
综合上面的解释,我们总结一下什么是RESTful架构:
(1)每一个URI代表一种资源;(2)客户端和服务器之间,传递这种资源的某种表现层(Representation);
(3)客户端通过四个HTTP动词,对服务器端资源进行操作,实现"表现层状态转化"。
3、REST,即Representational State Transfer的缩写。我对这个词组的翻译是"表现层状态转化"。REST是一种风格,而不是标准。REST通常基于使用HTTP,URI,和XML以及HTML这些现有的广泛流行的协议和标准。
a.资源是由URI来指定。
b.对资源的操作包括获取、创建、修改和删除资源,这些操作正好对应HTTP协议提供的GET、POST、PUT和DELETE方法。
c.通过操作资源的表现形式来操作资源。
d.资源的表现形式则是XML或者HTML,取决于读者是机器还是人,是消费web服务的客户软件还是web浏览器。当然也可以是任何其他的格式。
4、虽然REST受Web技术的影响很深,但是理论上REST架构风格并非绑定在HTTP上。然而,HTTP是唯一与REST相关的实例。基于该原因,本文描述了通过HTTP实现的REST,通常它也被称为RESTful HTTP。
5、任何重要的资源都应该能够通过一个唯一的标识被访问。RESTful HTTP使用URI来识别资源。URI提供了Web通用的识别机制,它包含了客户端直接与被引用的资源进行交互时需要的所有信息。
6、通过 REST 风格体系架构,请求和响应都是基于资源表示的传输来构建的。资源是通过全局 ID 来标识的,这些 ID 一般使用的是一个统一资源标识符(URI)。客户端应用使用 HTTP 方法(如,GET、POST、PUT 或 DELETE)来操作一个或多个资源。通常,GET 是用于获取或列出一个或多个资源,POST 用于创建,PUT 用于更新或替换,而 DELETE 则用于删除资源。
7、RESTful Web Service 是一个使用 HTTP 和 REST 原理实现的 Web Service。通常,一个 RESTful Web Service 将定义基本资源 URI、它所支持的表示/响应 MIME,以及它所支持的操作。
8、目前在三种主流的Web服务实现方案中,因为REST模式的Web服务与复杂的SOAP和XML-RPC对比来讲明显的更加简洁,越来越多的web服务开始采用REST风格设计和实现。其中,SOAP协议:简单对象访问协议(SOAP,全写为Simple Object Access Protocol)是一种标准化的通讯规范,主要用于Web服务(web service)中。SOAP的出现是为了简化网页服务器(Web Server)在从XML数据库中提取数据时,无需花时间去格式化页面,并能够让不同应用程序之间透过HTTP通讯协定,以XML格式互相交换彼此的数据,使其与编程语言、平台和硬件无关。XML-RPC是一个远程过程调用(远端程序呼叫)(remote procedure call,RPC)的分布式计算协议,通过XML将调用函数封装,并使用HTTP协议作为传送机制。后来在新的功能不断被引入下,这个标准慢慢演变成为今日的SOAP协定。
9、REST的优点
- 可以利用缓存Cache来提高响应速度
- 通讯本身的无状态性可以让不同的服务器的处理一系列请求中的不同请求,提高服务器的扩展性
- 浏览器即可作为客户端,简化软件需求
- 相对于其他叠加在HTTP协议之上的机制,REST的软件依赖性更小
- 不需要额外的资源发现机制
- 在软件技术演进中的长期的兼容性更好
10、创建 Java 的 RESTful Web Services有几种,如 Restlet、RestEasy 和 Jersey。Jersey 是其中最值得注意的,它是 JAX-RS(JSR 311)的参考实现。下面我们就来讲解一下通过此技术结合MyEclipse实现REST的过程:
File=>New=>Web Service Project。
project name: restblog,选择REST(JAX-RS) 下一步。
勾选core,jaxb,client,json然后下一步。
之所以创建servlet是因为会请求一些物理不存在的URI,事实上就是这个servlet在处理这些resource。
jaxb(Java Architecture for XML Binding)可以将java class序列号成xml,请参考http://java.sun.com/developer/technicalArticles/WebServices/jaxb/
json就不多说了,这个包可以实现java class 转化成json。
点击finish完成工程创建。
这样创建的工程与通常的web工程的区别就是添加了servlet在web.xml,这里贴出servlet代码:
同时,MyEcipse也会把相应的包添加至工程中,如下:<servlet> <display-name>JAX-RS REST Servlet</display-name> <servlet-name>JAX-RS REST Servlet</servlet-name> <servlet-class> com.sun.jersey.spi.container.servlet.ServletContainer </servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>JAX-RS REST Servlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping>
接下来我们创建一个简单的类Category
接下来我们新建一个Web Service,如图:
进入界面:
点击“Next>”
Java class : CategoryService
URL path: 资源路径 填写category ,这样所有与category相关的资源都使用http://yourpath/services/category访问
LIfecycle: Pre-request(JAX-RS default)每次请求时生成一个服务类实例(这里指CategoryService),singleton:单例,只存在一个实例
Consumes: 接受的contentType 包括application/xml ,application/json 通俗来说就是接受提交数据的格式。
Produces: 产生的数据格式选项同样为applicatin/xml,application/json 等。
注意这里的Consumes/Produces是指请求http://yourpath/services/category的数据格式。点击 Add 按钮添加服务方法,界面如下:
是一个构造服务方法的界面,下面有代码预览。在添加参数的时候,选择Param Type选项的意义:
Context: 这是一个标识该参数为请求上下文。可以直接获得request的参数。
QueryParam:uri?之后的参数
PathParam:uri中的参数如:/category/{id}中的id就是PathParam
FormParam:使用post提交的参数。
CookieParam: Cookie参数。
HeaderParam: 请求的头部信息。
也许你已经注意到没有session参数,是的,所谓的"无状态stateless"多少就体现在这里,web服务不保存请求相关的信息。
最后,生成的代码为:
之后,你也可以在CategoryService.java编辑中 右键菜单=>MyEclipse=>Add REST Method添加服务方法。@Path("category") public class CategoryService { @PUT @Path("add") @Produces("text/html") @Consumes({ "application/xml", "application/json" }) public String addCategory(@Context Category category) { throw new UnsupportedOperationException("Not yet implemented."); } }
右键工程根目录restblog选择MyEclipse=>Test with RESTful WebServices Explorer 将会打开测试窗口。
浏览中的地址为:http://localhost:8080/restblog/services/application.wadl 首先/services是由servlet拦截。
如果在浏览器中浏览该地址,将得到一个xml文件。这个文件叫web service application description language。顾名思义他是一个服务描述文件。
需要说明的是当调用方法的参数为Category类型,调用时传递的是xml同时contentType设置为application/xml这样jersey会将xml转化为Category类型的对象。如果转化出错则服务调用失败,会返回错误信息。
综合上面的用法,下面的例子涵盖了常用的服务方法的写法,之中的处理过程暂时用硬编码。
package com.zlb.rest.service; import java.util.ArrayList; import java.util.List; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.DefaultValue; import javax.ws.rs.FormParam; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.UriInfo; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; @Path("category") public class CategoryService { @GET @Produces({ "application/json", "application/xml" }) /** * getCategories 产生json,xml两种数据格式,具体那种格式取决于contentType */ public List<Category> getCategories() { List<Category> result = new ArrayList<Category>(); result.add(new Category(1, "第一个分类")); result.add(new Category(2, "第二个分类")); return result; } @GET @Path("{id}") public Category getCategory(@PathParam("id") int id) { return new Category(id, "id为" + id + "的类别"); } @GET @Path("json/{id}") @Produces("application/json") public JSONObject getCategoryJson(@PathParam("id") int id) {// 产生json JSONObject o = new JSONObject(); try { o.put("id", id); o.put("name", "id为" + id + "的category"); } catch (JSONException e) { e.printStackTrace(); } return o; } @PUT @Path("add") @Produces("text/html") @Consumes({ "application/xml", "application/json" }) public String addCategory(Category category) { System.out.println("处理添加类别逻辑,接受的数据为id:" + category.getId() + ",name:" + category.getName()); return "ok"; } @POST @Path("addbyname") public String addCategory( @FormParam("categoryname") @DefaultValue("[未命名]") String cateogryname) { System.out.println("处理添加类别逻辑,接受的数据为name:" + cateogryname); return "添加类别" + cateogryname + "成功"; } @POST @Produces("text/html") @Path("updatecategory") @Consumes({ "application/xml", "application/json" }) public String updateCategory(Category category) { System.out.println("处理更新类别逻辑,接受的数据为id:" + category.getId() + ",name:" + category.getName()); return "ok"; } @DELETE @Path("delete/{id}") public String deleteCategory(@PathParam("id") int id) { System.out.println("处理删除类别逻辑,接受的数据为id:" + id); return "ok"; } @GET @Path("commonProcess") public String commonProcess(@Context UriInfo info) {// @Context 参数标识UriInfo StringBuilder buf = new StringBuilder(); for (String param : info.getQueryParameters().keySet()) { buf.append(param + " : " + info.getQueryParameters().get(param)); buf.append("\n"); } System.out.println(buf.toString()); return "ok"; } }
参考资料:http://zh.wikipedia.org/wiki/RESThttp://www.ibm.com/developerworks/cn/web/wa-spring3webserv/index.html?ca=drs#download
http://www.blogjava.net/Hafeyang/archive/2009/02/05/253458.html
http://kb.cnblogs.com/page/114905/
http://www.infoq.com/cn/articles/designing-restful-http-apps-roth
http://www.oschina.net/news/27712/10-things-you-should-do-to-write-effective-restful-web-services/