用 Java 技術建立 RESTful Web 服務/@Path@Produces@PathParam

簡介java

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

在本文中,瞭解 JAX-RS 的主要組件。本文用一個例子展現了一個企業如何使用 JAX-RS 內的功能以一種 Restful 的方式公開員工的聯繫信息。正則表達式

背景編程

多年來,開發人員使用各類工具在其 Java 應用程序內建立 RESTful 服務。因爲 REST 架構的簡單性,主要需求 — 接收 HTTP 消息和頭部的能力 — 能夠由一個簡單的 Java Web 容器實現。json

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)。app

Java 資源ide

JAX-RS 創建了一種特殊的語言來描述資源,正如由其編程模型所表示的。有五種主要條目:根資源、子資源、資源方法、子資源方法以及子資源定位器。工具

根資源url

根資源是由 @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。

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。

 
				
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 的自身模型,而且是徹底以資源爲中心的。

爲了簡化基於 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。

相關文章
相關標籤/搜索