REST 是英文 Representational State Transfer 的縮寫,有中文翻譯爲「具象狀態傳輸」。REST 這個術語是由 Roy Fielding 在他的博士論文 《 Architectural Styles and the Design of Network-based Software Architectures 》中提出的。REST 並不是標準,而是一種開發 Web 應用的架構風格,能夠將其理解爲一種設計模式。REST 基於 HTTP,URI,以及 XML 這些現有的普遍流行的協議和標準,伴隨着 REST,HTTP 協議獲得了更加正確的使用。java
相較於基於 SOAP 和 WSDL 的 Web 服務,REST 模式提供了更爲簡潔的實現方案。目前,愈來愈多的 Web 服務開始採用 REST 風格設計和實現,真實世界中比較著名的 REST 服務包括:Google AJAX 搜索 API、Amazon Simple Storage Service (Amazon S3)等。程序員
基於 REST 的 Web 服務遵循一些基本的設計原則:web
Java EE 6 引入了對 JSR-311 的支持。JSR-311(JAX-RS:Java API for RESTful Web Services)旨在定義一個統一的規範,使得 Java 程序員能夠使用一套固定的接口來開發 REST 應用,避免了依賴於第三方框架。同時,JAX-RS 使用 POJO 編程模型和基於標註的配置,並集成了 JAXB,從而能夠有效縮短 REST 應用的開發週期。ajax
JAX-RS 定義的 API 位於 javax.ws.rs 包中,其中一些主要的接口、標註和抽象類如 圖 1所示。正則表達式
JAX-RS 的具體實現由第三方提供,例如 Sun 的參考實現 Jersey、Apache 的 CXF 以及 JBoss 的 RESTEasy。數據庫
在接下來的文章中,將結合一個記帳簿應用向讀者介紹 JAX-RS 一些關鍵的細節。編程
記帳簿示例應用程序中包含了 3 種資源:帳目、用戶以及帳目種類,用戶與帳目、帳目種類與帳目之間都是一對多的關係。記帳簿實現的主要功能包括:json
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 方法。api
@Path("/") public class BookkeepingService { ...... @Path("/person/") @POST @Consumes("application/json") public Response createPerson(Person person) { ...... } @Path("/person/") @PUT @Consumes("application/json") public Response updatePerson(Person person) { ...... } @Path("/person/{id:\\d+}/") @DELETE public Response deletePerson(@PathParam("id") int id) { ...... } @Path("/person/{id:\\d+}/") @GET @Produces("application/json") public Person readPerson(@PathParam("id") int id) { ...... } @Path("/persons/") @GET @Produces("application/json") public Person[] readAllPersons() { ...... } @Path("/person/{name}/") @GET @Produces("application/json") public Person readPersonByName(@PathParam("name") String name) { ...... } ......
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 方法合法的參數類型包括:
Resource 方法合法的返回值類型包括:
對於錯誤處理,Resource 方法能夠拋出非受控異常 WebApplicationException 或者返回包含了適當的錯誤碼集合的 Response 對象。
經過 Context 標註,根 Resource 類的實例字段能夠被注入以下類型的上下文資源:
要了解如何使用第 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 的結合使用。本文的示例應用使用了 Jersey 和 OpenJPA,部署在 Tomcat 容器上,替換成其它的實現只須要修改 web.xml 和 persistence.xml 配置文件。