天天看點

用 Java 技術建立 RESTful Web 服務(留着自己用)

簡介

JAX-RS (JSR-311) 是為 Java EE 環境下的 RESTful 服務能力提供的一種規範。它能提供對傳統的基于 SOAP 的 Web 服務的一種可行替代。

在本文中,了解 JAX-RS 的主要元件。本文用一個例子展示了一個企業如何使用 JAX-RS 内的功能以一種 Restful 的方式公開員工的聯系資訊。

回頁首

背景

多年來,開發人員使用各種工具在其 Java 應用程式内建立 RESTful 服務。由于 REST 架構的簡單性,主要需求 — 接收 HTTP 消息和頭部的能力 — 可以由一個簡單的 Java Web 容器實作。

Java servlets 常被用來開發 RESTful 應用程式。如何使用 servlet 并沒有固定的模式。通常,servlet 會接受請求并自己解析這個 HTTP 請求 URI,以将此請求與一個已知資源相比對。對于 REST 服務開發,這個簡單的 servlet 模型以更為正式的 API 得到擴充。但是,因為這些 API 是在 servlet 模型之上開發的,是以這些 API 中沒有一個是作為正式的标準開發的。

随着 REST 越來越多地被采用為一種架構,Java Community Process (JCP) 計劃在未來的 Java Enterprise Edition 6 釋出版中包括對 REST 的正式支援。JSR-311 也已建立好,并已有了 JAX-RS 1.0 規範,提供了一種新的基于注釋的方式來開發 RESTful 服務。與 servlet 模型相比,JAX-RS 注釋讓您能集中于您的資源和資料對象。并且,您不必再開發通訊層(通過 servlet)。

回頁首

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:[email protected]+\\.[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

代表的是正規表達式

[email protected]+\\.[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:[email protected]+\\.[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/[email protected] HTTP/1.1
Accept: application/json
			      

在清單 10 中,對 /contacts/[email protected] 的

GET

請求表明了

getByEmailAddress

方法将會被調用并且傳回的格式将會是

application/json

,而非 text/xml。

回頁首

Providers

JAX-RS 提供程式是一些應用程式元件,允許在三個關鍵領域進行運作時行為的定制:資料綁定、異常映射以及上下文解析(比如,向運作時提供 JAXBContext 執行個體)。每個 JAX-RS 提供程式類必須由

@Provider

注釋。如下的例子讨論了兩個資料綁定提供程式

MessageBodyWriter

MessageBodyReader

MessageBodyWriter

MessageBodyWriters 被 JAX-RS 運作時用來序列化所傳回資源的表示。遵從 JSR-311 的運作時提供了對常見類型(java.lang.String、java.io.InputStream、 JAXB 對象等)的本機支援,但使用者也可以向 JAX-RS 運作時提供他或她自己的 MessageBodyWriter。比如,您可以提供一個定制

MessageBodyWriter

來處理定制

ContactInfo

Java 類型,如下所示。

清單 11. 定制 MessageBodyWriter

package com.ibm.jaxrs.sample.organization;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Provider;

@Provider
@Produces("text/xml")
public class ContactInfoWriter implements MessageBodyWriter<ContactInfo> {

	public long	getSize(T t, java.lang.Class<ContactInfo> type, 
		java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] 
		annotations, MediaType mediaType)  {
		...
	}
	
	public boolean isWriteable(java.lang.Class<ContactInfo> type, 
		java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] 
		annotations, MediaType mediaType) {
		return true;
	}
	
	public void writeTo(ContactInfo contactInfo, java.lang.Class<ContactInfo> type, 
		java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] 
		annotations, MediaType mediaType, MultivaluedMap<
		java.lang.String, java.lang.Object> httpHeaders, java.io.OutputStream 
		entityStream) {
		contactInfo.serialize(entityStream);
	}
}
					      

ContactInfoWriter

則在所傳回的資源表示被序列化之前由 JAX-RS 運作時調用。如果

isWriteable

傳回 true 且

@Produces

是此資源方法的

@Produces

值最為接近的比對,就會調用

writeTo

方法。在這裡,

ContactInfoWriter

負責向底層的

OutputStream

序列化

ContactInfo

執行個體的内容。

MessageBodyReader

MessageBodyReaders 則與 MessageBodyWriters 相反。對于反序列化,JAX-RS 運作時支援與序列化相同的類型。使用者也可以提供他或她自己的 MessageBodyReader 實作。MessageBodyReader 的最主要的功能是讀取請求

InputStream

并将傳入的位元組反序列化到一個此資源方法期望的 Java 對象。

ContactInfo

類型的

MessageBodyReader

可以類似于清單 12。

清單 12. 定制 MessageBodyReader

package com.ibm.jaxrs.sample.organization;

import javax.ws.rs.Consumes;
import javax.ws.rs.Produces;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;

@Provider
@Consumes("text/xml")
public class ContactInfoReader implements MessageBodyReader<ContactInfo> {

	public boolean isReadable(java.lang.Class<ContactInfo> type, 
		java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] 
		annotations, MediaType mediaType) {
		return true;
	}
	
	public ContactInfo readFrom(java.lang.Class<ContactInfo> type, 
		java.lang.reflect.Type genericType, java.lang.annotation.Annotation[] 
		annotations, MediaType mediaType, MultivaluedMap<
		java.lang.String,java.lang.String> httpHeaders, java.io.InputStream 
		entityStream) {
		return ContactInfo.parse(entityStream);
	}
	
}
					      

與 MessageBodyWriter

isWriteable

類似,

ContactInfoReader

isReadable

方法将被調用以便決定 MessageBodyReader 能否處理此輸入。如果

isReadable

傳回 true 且

@Consumes

值與此資源方法的

@Consumes

值最為比對,就會選擇

ContactInfoReader

。當

readFrom

方法被調用時,結果會是基于請求

InputStream

的内容建立

ContactInfo

執行個體。

回頁首

配置

至此,我們探讨了 JAX-RS 資源類和一些提供程式類(MessageBodyReaders 和 MessageBodyWriters)。那麼,該如何在 JAX-RS 運作時内配置這些類呢?這可以通過擴充 javax.ws.rs.core.Application 類實作。此類提供了一組類或一組單例(singleton)對象執行個體,在一個 JAX-RS 應用程式内包括所有的 根級别的資源和提供程式(由

@Provider

注釋的類)。若為這個示例聯系資訊應用程式擴充這個 Application 類,它應該類似于清單 13。

清單 13. ContactInfoApplication

package com.ibm.jaxrs.sample.organization;

import java.util.HashSet;
import java.util.Set;
import javax.ws.rs.core.Application;

public class ContactInfoApplicaiton extends Application {

	public Set<Class<?>> getClasses() {
		Set<Class<?>> classes = new HashSetSet<Class<?>>();
		classes.add(ContactsResource.class);
		classes.add(ContactInfoWriter.class);
		classes.add(ContactInfoReader.class);
	}
	
	public SetSet<Object<?>> getSingletons() {
		// nothing to do, no singletons
	}
	
}
					      

getClasses

方法為 JAX-RS 運作時提供了一組可用于中繼資料的類。請注意,

getSingletons

方法什麼都不傳回。通常而言,将 JAX-RS 提供程式視為單例是沒有問題的,但将一個 JAX-RS 資源視為單例則要格外謹慎。常被 JAX-RS 資源類使用的基于注釋的注入可能在一個單例執行個體的情況内并不受支援。是以,除非仔細計劃,否則應該避免使用 JAX-RS 資源的單例執行個體。

假設,您正在一個 servlet 容器内部署一個 JAX-RS 應用程式,有兩種方法可以向 JAX-RS 運作時注冊您的 javax.ws.rs.core.Application 子類。這是由 WAR 檔案的 web.xml 處理的,如下所示。

清單 14. 不能感覺 JAX-RS 的 servlet 容器

<web-app id="WebApp_ID" version="2.5"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi=
	"http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<servlet>
		<servlet-name>ContactInfoServlet</servlet-name>
		<servlet-class>com.sample.RESTSystemServlet</servlet-class>
		<init-param>
			<param-name>javax.ws.rs.Application</param-name>
			<param-value>
				com.ibm.jaxrs.sample.organization.ContactInfoApplication
			</param-value>
		</init-param>
	</servlet>
	<servlet-mapping>
		<servlet-name>ContactInfoServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>
					      

在一個被認為是不能感覺 JAX-RS 的 servlet 容器内,應該作為 servlet 定義内的 init-param 提供 Application 子類名。init-param 的名字必須是 javax.ws.rs.Application。servlet 類則很可能是 JAX-RS 運作時系統 servlet。您可以列出每個可能的 URL 模式,或者使用

/*

通配符注冊,如下所示。

清單 15. 能感覺 JAX-RS 的 servlet 容器

<web-app id="WebApp_ID" version="2.5"
	xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi=
	"http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
	http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
	<servlet>
		<servlet-name>ContactInfoServlet</servlet-name>
		<servlet-class>
			com.ibm.jaxrs.sample.organization.ContactInfoApplication
		</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>ContactInfoServlet</servlet-name>
		<url-pattern>/*</url-pattern>
	</servlet-mapping>
</web-app>
					      

在一個被認為是能感覺 JAX-RS 的 servlet 容器内,必須作為 servlet 定義内的 servlet 類元素的值提供 Application 子類名。您仍然可以選擇是列出每個可能的 URL 模式還是使用

/*

通配符注冊。

以 Apache Wink 作為運作時的 JAX-RS

下一步是找到一個能夠支援 JAX-RS 内的可用功能的運作時。Apache Wink 項目就提供了一個能滿足這種要求的運作時,具有上面所述的所有特性(參見 參考資料)。起初,Wink 是由開源社群的多個廠商和成員發起的一個協作項目。該項目的目的是提供最為靈活和輕量級的運作時。

除了标準 JAX-RS 特性之外,Wink 還提供了對 JSON、Atom 和 RSS 序列化格式的增強支援。JAX-RS 本身并不提供客戶機 API,但 Wink 包括了其對客戶機 API 的自身模型,并且是完全以資源為中心的。

在 My developerWorks 上加入 Apache Wink 組

在 My developerWorks Apache Wink 組 中與其他開發人員進行有關如何開發 Apache Wink 的主題讨論和資源共享。

還不是 My developerWorks 的成員? 現在就加入!

為了簡化基于 Wink 的服務的開發,可以下載下傳 Wink 1.0 庫并将它們作為預設 JAX-RS 庫包括到 Rational Application Developer (RAD) 7.5.5 開發環境(參見 參考資料)中。在這個更新版本中,RAD 添加了一個 JAX-RS facet,可供您進行配置以支援驗證器和注釋幫助。這個新的 facet 還能通過自動生成所需的 servlet 項和映射來簡化 servlet 的配置。

結束語

與傳統的 servlet 模型相比,JAX-RS 提供了一種可行的、更為簡便、移植性更好的方式來在 Java 内實作 RESTful 服務。使用注釋讓您能夠輕松提供 Java 資源的路徑位置并将 Java 方法綁定到 HTTP 請求方法。一種可移植的資料綁定架構提供了一些本機的 Java 類型支援并允許進行序列化/反序列化處理的完全定制。javax.ws.rs.core. Application 子類的擴充以及 web.xml 内的相應清單表明了用最少的部署描述符配置就能進行輕松部署。

本文隻涉及了 JAX-RS 所能提供功能的一部分。就提供應用程式上下文(比如 JAXBContext 執行個體)并将運作時異常映射給 HTTP 請求而言,其他兩個 JAX-RS 提供程式類型

ContextResolvers

ExceptionMappingProviders

還能提供對應用程式元件的進一步控制。注釋的定義是為了控制方法參數和類成員的注入,它們在運作時的整個過程向應用程式提供了有價值的上下文資訊。總的來說,JAX-RS 必将是一種面向基于 Java 的 RESTful 服務開發的簡便、可移植的、全面的 API。

參考資料

學習

  • 探索 Java.net 上的 JSR-311 項目,獲得 API 和規範以及與這個專家組進行溝通的郵件清單。
  • 閱讀 “Architectural Styles and the Design of Network-based Software Architectures”,這是 Roy Fielding 所寫的一篇有關 REST 的專題論文,引入了 RESTful 架構的概念。
  • 更多地了解 Apache Wink。
  • “基于 REST 的 Web 服務:基礎”提供了對 REST 基本原理的簡介。
  • Bill Burke 撰寫的 RESTful Java with JAX-RS(O'Reilly Media,2009 年 11 月)是一本有關使用 RESTful 架構原理以及 Java EE 6 内的 JAX-RS 規範用 Java 設計和開發分布式 Web 服務的實用參考。
  • developerWorks Web 開發專區 内有豐富的面向 Web 2.0 的工具和資訊。
  • IBM 技術活動和網絡廣播:随時關注 developerWorks 的技術活動和網絡廣播。
  • My developerWorks:個性化您的 developerWorks 體驗。

獲得産品和技術

  • Apache Wink 架構:下載下傳此架構來建構 RESTful Web 服務。
  • IBM 産品評估版:現在就下載下傳這些版本并開始嘗試使用來自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的應用程式開發工具和中間件産品。

讨論

  • 查閱 developerWorks blogs 并加入 developerWorks 社群。

作者簡介

用 Java 技術建立 RESTful Web 服務(留着自己用)
用 Java 技術建立 RESTful Web 服務(留着自己用)

Dustin Amrhein 剛加入到 IBM 時是一名 WebSphere Application Server 開發團隊的成員。在擔任此職位期間,Dustin 從事 Web 服務基礎設施以及 Web 服務程式設計模型的開發。此外,Dustin 負責 Java RESTful 服務架構開發的技術工作。Dustin 目前擔任的角色是 IBM 的 WebSphere 産品組合中的新興技術的推廣者。他目前主要關注傳遞雲計算功能的 WebSphere 技術,包括 WebSphere CloudBurst Appliance。

用 Java 技術建立 RESTful Web 服務(留着自己用)

Nick Gallardo 是德克薩斯州奧斯汀市的一名軟體工程師。他自 WebSphere Application Server 版本 5.0 開始就一直從事 WebSphere Application Server 的各個方面。Nick 是 Apache Axis2 開源項目的貢獻者并一直從事 WebSphere 的 REST 支援和 JAX-RS。