在Java上实现RESTful Web Services


这篇技术文章将为你展示如何在 Java 上使用 JAX-RS: Java API for RESTful Web Services (JSR-311)  和它的参考实现 - Jersey 来编写 RESTful 风格的 Web Services。你可以学习到一些 Representational State Transfer (REST) 的设计原则和 JAX-RS与 Jersey 的入门知识。

这篇文章使用了一个简单的示例程序来演示一些 JAX-RS 的概念和技术。你可以从 Jersey 下载页面下载最新版本的 Jersey 快照来得到这些示例。这篇文章所使用的示例源代码可以在示例程序中找到(下载包包含了示例程序)。

RESTful Web Services 简介

Representational State Transfer (REST) 与万维网 (World Wide Web) 类似,是一种分布式系统的软件架构方式。这个名词是Roy Fielding在他2000年的博士论文中提出的,现如今已经在互联网社区广为人知。REST的一个重要概念是对资源(resources)的扩展,如可以用一个全局的定位符来引用,即所谓的URI。为了维护这些资源,网络组件之间、服务器端与客户端之间通过一种标准接口(如HTTP)来交流和交换这些资源的表现(representations)。

RESTful web services 即使用RESTful架构风格构建的服务。由于其轻量的特性和在HTTP上直接传输数据的能力,在互联网服务部署技术的选择上,使用RESTful风格构建服务正在逐渐成为基于SOAP技术的有力挑战者。

RESTful Web Service 原则

一个 RESTful web service 是符合以下原则的:

    * 资源与表现。与以往仅仅为每个 web service 定义一个端点(endpoint)并且由该端点来执行相应的操作不同,你必须提供的是资源的访问方式。一个资源意味着可由客户端访问的web程序的一部分。因为不可能在网络上直接进行的资源的传输,所以我们实际上提供的是资源的表现。
    * 可定位性与互连接性。资源具备表现,同时也必须具备一个用于定位的地址。在 REST 中,每个资源至少必须拥有一个地址,即 URI。只须指定 URI 便可定位到资源,这便是所谓的“可定位性”。在发布Web程序的时候,我们同时发布许多不同互相连接在一起的URI。因为互连接性,我们只须提供给客户端一个“引导 URI”便可以访问所有的 URI。
    * 同样形式的接口。除了资源的表现和URI之外,我们还需要一个连接协议来为服务建立连接。在REST架构中,所有资源都使用同一套方法进行链接,即无论你访问哪一个 URI,都必须使用同一套连接接口。以实际应用为例,无论你访问任何一个万维网地址,你的网络浏览器总是使用 HTTP GET 方法来返回和显示相应的 web 页面。
    * 无状态性。无状态性意味着一个 REST 的 web 程序不会为客户端保留任何信息。REST 不应使用 HTTP 会话(sessions)。每个客户端负责保存自己的状态(如果有需要的话)。服务端只维护资源和向客户端提供统一形式的接口。

JAX-RS 与 Jersey

JAX -RS为 在 Java 上构建 RESTful 风格的 web services 提供了一组标准 API。这组API基本上由一组注解(annotations)和相关的类和接口组成的。我们可以通过为 POJO 添加注解来发布 web services。目前这组 API 还尚未完成,最终的完成版本会成为 Java EE 6 的一部分。可以在这里找到更多关于 JAX-RS 的信息。

Jersey 是 JAX-RS 的参考实现。你可以从 Jersey 的下载页面找到它的分发包。如果你下载了最新版本的 Jersey 快照并解压缩,你会看到 Jersey 的实现与一些示例程序。我们可以从这些示例程序学习如何使用 Jersey。下面让我们开始看看其中一个示例。

JAX-RS 示例: The Bookmark Application

Bookmark 程序是随着 Jersey 发布的其中一个示例。我们可以在 examples/Bookmark 目录下找到它。这个示例程序演示了如何使用 JAX-RS API 来维护用户保存的书签信息。如果我们运行这个程序并且指定一个用户,便宜得到如下的返回信息:

   {sdesc":"test desc","userid":"testuserid","uri":
   "http://java.sun.com","ldesc":"long test description"}

这里使用了 JavaScript Object Notation (JSON) 数据封装格式.

浏览 examples\Bookmark\src\java\com\sun\ws\rest\samples\bookmark\resources 目录,会发现以下这些资源:

    * UsersResource: 表现了用户列表
    * UserResource: 表现了一个特定的用户
    * BookmarksResource: 表现一个特定用户的书签列表
    * BookmarkResource: 表现了一个特定的书签

前面提到了我们可以通过指定URI来定位资源。然而,要访问一个资源,我们还需要指定连接协议,如 HTTP。下面是 Bookmark 程序中资源对应的 URI 和 HTTP 方法(methods):
Resource

URI Path

HTTP Methods
UsersResource

/users

GET
UserResource

/users/{userid}

GET, PUT, DELETE
BookmarksResource

/users/{userid}/bookmarks

GET, POST
BookmarkResource

/users/{userid}/bookmarks/{bmid}

GET, PUT, DELETE

为了了解 JAX-RS 的基础知识,首先让我们来关注其中两个资源: UsersResource and UserResource.

UsersResource

这是 UsersResource 类的代码片断:

   @UriTemplate("/users/")
   public class UsersResource {

       @HttpContext UriInfo uriInfo;  
       @PersistenceUnit(unitName = "BookmarkPU")
       EntityManagerFactory emf;
       /** Creates a new instance of Users */
       public UsersResource() {
       }

       public List getUsers() {
           return emf.createEntityManager().createQuery(
                  "SELECT u from UserEntity u").getResultList();
       }

       @UriTemplate("{userid}/")
       public UserResource getUser(@UriParam("userid")
              String userid) {
           return new UserResource(
                  uriInfo, emf.createEntityManager(), userid);
       }
       @HttpMethod("GET")
       @ProduceMime("application/json")
       public JSONArray getUsersAsJsonArray() {
           JSONArray uriArray = new JSONArray();
           UriBuilder ub = null;
           for (UserEntity userEntity : getUsers()) {
               ub = (ub == null) ?
                      uriInfo.getBuilder() : ub.clone();
               URI userUri = ub.
                       path(userEntity.getUserid()).
                       build();
               uriArray.put(userUri.toString());
           }
           return uriArray;
       }
   }

在上面这段代码中,UsersResource 类使用了 @UriTemplate("/users/") 注解。@UriTemplate 注解定义资源的URI路径。在上面的例子里定义了URI路径为/users/:

   @UriTemplate("/users/")

Annotating the class with a @UriTemplate annotation makes the class a "Root resource class." It also means that for client requests that access the /users/ URI path, this resource is responsible for providing appropriate responses. Note too that the /users/ URI path is the bootstrap URI path for the entire Bookmark web application.

标注 @UriTemplate 注解使得该类成为“根资源类”。这意味着该类负责响应客户端对访问 /users/ URI 路径的请求。要注意到 /users/ URI 路径同时也是整个 Bookmark 程序的“引导 URI”。

另一个在 UsersResource 用到的 JSR-311 注解是 @HttpContext。

   @HttpContext UriInfo uriInfo;

这个注解把信息注入给类的属性和方法的参数。在 UsersResource 类中,@HttpContext 注解把有关URI的信息注入到 uriInfo 变量中。

UsersResource 类中的方法

UsersResource 类拥有两个方法,getUser 和 getUsersAsJsonArray。现上让我们暂时忘记 getUser 而先来关注一下 getUsersAsJsonArray。getUsersAsJsonArray 方法返回所有用户资源的 URI 列表。这个方法使用了两个 JSR 311 注解:@HttpMethod 和 @ProduceMime。@HttpMethod 注解指定了被注解的方法应使用HTTP requests方式来处理和响应。在这个例子中,指定了 getUsersAsJsonArray 方法响应(serves,在这里不是很确定这个词怎么翻) HTTP GET 请求。

   @HttpMethod("GET")

像这样的响应 REST 请求的方法我们称之为“资源方法”(Resource methods)。

@ProduceMime 注解一个方法的返回(produce,同样翻得很心虚-_-!)指定了 MIME 类型。在上面上例子里,指定了 getUsersAsJsonArray 方法返回一个 JSON 数组对象,其中的内容是所有存在的用户资源。

   @ProduceMime("application/json")

Get Users Resources

Get Users Resources

这个方法返回的 JSON 数组对象看起来应该是这样:

   ["http://localhost:8080/Bookmark/resources/users/joe",
   "http://localhost:8080/Bookmark/resources/users/mary"]

这个 JSON 数组对象包含了一组 URI, 或者说链接,它们是 joe 和 mary 这两个用户资源。

getUser 方法可以取得一个指定的用户资源。例如,如果客户端需要取得用户 joe 的信息,那么客户端可以访问以下 URI:http://localhost:8080/Bookmark/resources/users/joe。在上面我们已经提到过 UsersResource 类会响应所有以 /users/ 开头的 URI 路径,包括了 joe 的 URI 路径,亦即 /users/joe。

这里很重要的一点是 getUser 方法使用了 @UriTemplate("{userid}/") 这样的注解,使这个方法成了一个“子资源定位器”(Sub-resource locator)。同时 getUser 也使用了 @UriParam,这使得当 getUser 方法被调用时,当前请求 URI 路径中是 userid 的值将被注入到 userid 参数。

必须注意到 @HttpMethod 注解没有被关联到 getUser 方法。因此,可以认为这个方法输出的是一个资源类的对象。这意味着对该请求的处理会被代理到一个资源类和相应的被 @HttpMethod 注解的方法。因为 getUser 方法返回一个 UserResource 对象 (注意User后少了一个s):

   public UserResource getUser(@UriParam("userid")
                 String userid) {
           return new UserResource(...)

UserResource 类中的相应的方法将被调用。

Get User Resources

Get User Resources

UserResource

如前文所述,在 UsersResource 类中对 getUser 方法的请求会被代理到一个新的 UserResource 实例中相应的方法。下面是 UserResource 类中 getUser 方法的代码片断。

   @HttpMethod("GET")
   @ProduceMime("application/json")
   public JSONObject getUser() throws JSONException {
       if (null == userEntity) {
           throw new NotFoundException(
                  "userid " + userid + "does not exist!");
       }
       return new JSONObject()
           .put("userid", userEntity.getUserid())
           .put("username", userEntity.getUsername())
           .put("email", userEntity.getEmail())
           .put("password", userEntity.getPassword())
           .put("bookmarks",
                uriInfo.getBuilder().path("bookmarks").build());
    }

可以注意到这个方法被使用了 @HttpMethod("GET") 和 @ProduceMime("application/json") 注解。在这里 getUsers 方法响应了 HTTP GET 请求并且返回一个 JSONObject 对象。这个 JSONObject 对象包含了具体用户资源的表现,具体来说,即 userid 为 joe 的用户资源的表现。

你还可以继续观察 UserResource 类余下的源代码。你会发现其它 JSR 311 注解,比如用于定义方法可以接受的 MIME 类型的 @ConsumeMime 注解。

创建与部署示例代码

这段示例代码已经配置为一个 NetBeans 工程. 你可以在 NetBesns IDE 中或使用命令行创建并部署这个示例。无论哪种情况,需要具备:

   1. 如果你还没有 GlassFish V2,下载并安装它。
   2. 从 Jersey 下载页面下载最新版本的 Jersey 快照并解压缩。

(以下这几段不翻了)

Building and Deploying the Sample Code in NetBeans

   1. If you haven't already done so, download and install the NetBeans 5.5.1 IDE.
   2. Start the NetBeans IDE. If you haven't already done so, register GlassFish V2 in NetBeans as follows:
          * Right click on Servers node in the Runtime window.
          * Select Add Server.
          * Leave the Server as Sun Java System Application Server.
          * Click the Next button.
          * Click the Browse button and browse to the location that you installed GlassFish V2.
          * Click the Choose button.
          * Click the Next button.
          * Set the Admin Password to the default, adminadmin, unless you chose a different password for GlassFish.
          * Click the Finish button.
   3. Open the Bookmark project as follows:
          * Select Open Project from the File menu.
          * Browse to the Bookmark subdirectory.
          * Click the Open Project Folder button.
   4. Build and deploy the Bookmark project as follows:
          * Right click the Bookmark project node in the Projects window.
          * Select Deploy Project or press F6 (Run Main Project).

Building and Deploying the Sample Code From the Command Line

   1. Set the AS_HOME environment variable to the GlassFish v2 installation directory, for example, (here shown in bash syntax):
            export AS_HOME=
      where is the directory where you installed GlassFish v2.
   2. Navigate below the /jersey directory to the /examples/Bookmark directory. Build the Bookmark application by entering the following command on the command line (here shown in bash syntax):
            AS_HOME/lib/ant/bin/ant run-on-glassfish

Running the Sample Code

You can run the deployed Bookmark application as follows using Curl, a command line HTTP tool.

   1. If you haven't already done so, download Curl.
   2. Add a new user by entering the following command on the command line (note that the commands in this and subsequent steps are shown on multiple lines for formatting purposes):
            curl -i --data "{\"userid\":\"techtip\",\"username\":
            \"TechTip User\",\"email\":\"[email protected]\",
            \"password\":\"TEST\"}" -H Content-type:application/json
            -X PUT
            http://localhost:8080/Bookmark/resources/users/techtip/
      In response, an HTTP GET request is dispatched to the getUser method in the UsersResource class, which instantiates a new UserResource object. The request is further dispatched to the putUser method.
      You should see output similar to the following:
            HTTP/1.1 204 No Content
            X-Powered-By: Servlet/2.5
            Server: Sun Java System Application Server 9.1_01
            Date: Thu, 01 Nov 2007 14:31:53 GMT
   3. Get a list of users by entering the following command on the command line:
            curl -i -X GET
            http://localhost:8080/Bookmark/resources/users/
      This invokes the getUsersListAsJson method of the UsersResource class.
      You should see output similar to the following:
            HTTP/1.1 200 OK
            X-Powered-By: Servlet/2.5
            Server: Sun Java System Application Server 9.1_01
            Content-Type: application/json
            Transfer-Encoding: chunked
            Date: Thu, 01 Nov 2007 14:34:07 GMT
            ["http:\/\/localhost:8080\/Bookmark\/resources\/users\
            /techtip"]
   4. Get the representation of a user by entering the following command on the command line :
            curl -i -X GET
            http://localhost:8080/Bookmark/resources/users/techtip/
      The resulting actions here are similar to those for step 2.
      You should see output similar to the following:
            HTTP/1.1 200 OK
            X-Powered-By: Servlet/2.5
            Server: Sun Java System Application Server 9.1_01
            Content-Type: application/json
            Transfer-Encoding: chunked
            Date: Thu, 01 Nov 2007 14:35:38 GMT
            {"userid":"techtip","username":"TechTip User",
            "email":"[email protected]","password":"TEST",
            "bookmarks":"http:\/\/localhost:8080\/Bookmark\/resources
            \/users\/techtip\/bookmarks"}

总结

这篇文章演示了如何在 Java 中使用编写符合 JAX-RS (JSR-311) 规范的 RESTful 风格的 web services。你可以从 jsr311 project 学习更多有关 JAX-RS 的内容,也可以从 Jersey project,一个 JAX-RS 的参考实现,学习更多有关 Jersey 的内容。

About the Authors

Jakub Podlesak is a member of the Jersey project team. Previously, he participated in the development of Metro, the GlassFish v2 web services stack, as a member of the WS-Policy team.

Paul Sandoz is the co-spec lead and implementation lead for JSR 311: Java API for RESTful Web Services. He has participated in the W3C, ISO, and ITU-T standards organizations and contributed various performance-related technologies and improvements to the GlassFish web services stack, particularly in standardization, implementation, integration, and interoperability of Fast Infoset.

你可能感兴趣的:(RESTful,Web,Services)