用Java技术创建RESTful Web服务

日期: 2010-06-12 作者:Nick Gallardo&Dustin Amrhein 来源:TechTarget中国 英文

  多年来,开发人员使用各种工具在其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 规范提供的注释的完整列表。

  @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。

  清单 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″
  >
 <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″
  >
 <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。

我们一直都在努力坚持原创.......请不要一声不吭,就悄悄拿走。

我原创,你原创,我们的内容世界才会更加精彩!

【所有原创内容版权均属TechTarget,欢迎大家转发分享。但未经授权,严禁任何媒体(平面媒体、网络媒体、自媒体等)以及微信公众号复制、转载、摘编或以其他方式进行使用。】

微信公众号

TechTarget微信公众号二维码

TechTarget

官方微博

TechTarget中国官方微博二维码

TechTarget中国

相关推荐