Java EE 6 引入了对 JSR-311 的支持。JSR-311(JAX-RS:Java API for RESTful Web Services)旨在定义一个统一的规范,使得 Java 程序员可以使用一套固定的接口来开发 REST 应用,避免了依赖于第三方框架。同时,JAX-RS 使用 POJO 编程模型和基于标注的配置,并集成了 JAXB,从而可以有效缩短 REST 应用的开发周期。
JAX-RS 定义的 API 位于 javax.ws.rs 包中,其中一些主要的接口、标注和抽象类如 所示。
图 1. javax.ws.rs 包概况
JAX-RS是一套用java实现REST服务的规范,提供了一些标注将一个资源类,一个Java类,封装为Web资源。标注包括:
- @Path,标注资源类或方法的相对路径
- @GET,@PUT,@POST,@DELETE,标注方法是用的HTTP请求的类型
- @Produces,标注返回的MIME媒体类型
- @Consumes,标注可接受请求的媒体类型
- @PathParam,@QueryParam,@HeaderParam,@CookieParam,@MatrixParam,@FormParam,分别标注方法的参数来自于HTTP请求的不同位置,例如@PathParam来自于URL的路径,@QueryParam来自于URL的查询参数,@HeaderParam来自于HTTP请求的头信息,@CookieParam来自于HTTP请求的Cookie。
目前JAX-RS的实现包括:
- ,开源的框架。
- , 由提供的JAX-RS的参考实现。
- ,的实现。
- ,由Jerome Louvel和Dave Pawson开发,是最早的REST框架,先于JAX-RS出现。
- ,一个孵化器中的项目,其服务模块实现JAX-RS规范
示例简介
记账簿示例应用程序中包含了 3 种资源:账目、用户以及账目种类,用户与账目、账目种类与账目之间都是一对多的关系。记账簿实现的主要功能包括:
- 记录某用户在什么时间花费了多少金额在哪个种类上
- 按照用户、账目种类、时间或者金额查询记录
- 对用户以及账目种类的管理
Resource 类和 Resource 方法
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。展示了示例应用中使用的根 Resource 类及其 Resource 方法。
清单 1. 根 Resource 类
@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 中的模板变量映射到方法参数,模板变量支持使用正则表达式,变量名与正则表达式之间用分号分隔。例如对 中所示的 BookkeepingService 类,如果使用 Get 方法请求资源”/person/jeffyin”,则 readPersonByName 方法将被调用,方法参数 name 被赋值为”jeffyin”;而如果使用 Get 方法请求资源”/person/123”,则 readPerson 方法将被调用,方法参数 id 被赋值为 123。要了解如何使用其它的参数标注 , 请参考 。
JAX-RS 规定 Resource 方法中只允许有一个参数没有打上任何的参数标注,该参数称为实体参数,用于映射请求体。例如 清单 1 中所示的 BookkeepingService 类的 createPerson 方法和 updatePerson 方法的参数 person。
参数与返回值类型
Resource 方法合法的参数类型包括:
- 原生类型
- 构造函数接收单个字符串参数或者包含接收单个字符串参数的静态方法 valueOf 的任意类型
- List<T>,Set<T>,SortedSet<T>(T 为以上的 2 种类型)
- 用于映射请求体的实体参数
Resource 方法合法的返回值类型包括:
- void:状态码 204 和空响应体
- Response:Response 的 status 属性指定了状态码,entity 属性映射为响应体
- GenericEntity:GenericEntity 的 entity 属性映射为响应体,entity 属性为空则状态码为 204,非空则状态码为 200
- 其它类型:返回的对象实例映射为响应体,实例为空则状态码为 204,非空则状态码为 200
对于错误处理,Resource 方法可以抛出非受控异常 WebApplicationException 或者返回包含了适当的错误码集合的 Response 对象。
Context 标注
通过 Context 标注,根 Resource 类的实例字段可以被注入如下类型的上下文资源:
- Request、UriInfo、HttpHeaders、Providers、SecurityContext
- HttpServletRequest、HttpServletResponse、ServletContext、ServletConfig
要了解如何使用第 1 种类型的上下文资源 , 请参考 。
CRUD 操作
JAX-RS 定义了 @POST、@GET、@PUT 和 @DELETE,分别对应 4 种 HTTP 方法,用于对资源进行创建、检索、更新和删除的操作。
POST 标注
POST 标注用于在服务器上创建资源,如 所示。
清单 2. POST 标注
@Path("/") public class BookkeepingService { ...... @Path("/account/") @POST @Consumes("application/json") public Response createAccount(Account account) { ...... } ......
如果使用 POST 方法请求资源”/account”,则 createAccount 方法将被调用,JSON 格式的请求体被自动映射为实体参数 account。
GET 标注
GET 标注用于在服务器上检索资源,如 所示。
清单 3. GET 标注
@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 标注
PUT 标注用于更新服务器上的资源,如 所示。
清单 4. PUT 标注
@Path("/") public class BookkeepingService { ...... @Path("/account/") @PUT @Consumes("application/json") public Response updateAccount(Account account) { ...... } ......
如果使用 PUT 方法请求资源”/account”,则 updateAccount 方法将被调用,JSON 格式的请求体被自动映射为实体参数 account。
DELETE 标注
DELETE 标注用于删除服务器上的资源,如 所示。
清单 5. DELETE 标注
@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,如 所示,GsonProvider 类使用了 Google Gson 作为 JSON 格式的 MessageBodyProvider 的实现。
清单 6. GsonProvider
@Provider @Produces("application/json") @Consumes("application/json") public class GsonProvider implements MessageBodyWriter
JAX-RS 与 JPA 的结合使用
由于 JAX-RS 和 JPA 同样都使用了基于 POJO 和标注的编程模型,因而很易于结合在一起使用。示例应用中的 Web 资源 ( 如账目 ) 同时也是持久化到数据库中的实体,同一个 POJO 类上既有 JAXB 的标注,也有 JPA 的标注 ( 或者还有 Gson 的标注 ) ,这使得应用中类的个数得以减少。如 所示,Account 类可以在 JAX-RS 与 JPA 之间得到复用,它不但可以被 JAX-RS 绑定为请求体 / 响应体的 XML/JSON 数据,也可以被 JPA 持久化到关系型数据库中。
清单 7. Account
@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 配置文件。