本文侧重讲解JAX-RS的编程元素,而非如何实现规范
JAX-RS是什么
JAX-RS(Java API for RESTful Web Service,JSR-311)是Java提供用于开发RESTful Web服务基于注解(annotation)的API,在Java EE 6中发布,旨在定义一个统一的规范,使得Java程序员可以使用一套固定的接口来开发REST应用,避免了依赖第三方框架,同时JAX-RS使用POJO编程模型和基于注解的配置并集成JAXB,从而有效缩短了REST应用的开发周期,JSR-311开始于2007年2月,至今发布了两个最终版本1.0,1.1,值得注意的是JAX-RS2.0(JSR-339,始于2011年1月)正在进行中;
(顺便说下,Java之所以称为开放技术就是因为JSR(Java Specification Requests),任何组织和个人都能向JCP(Java Community Process)提起JSR,经专家组评审通过后可以在后续Java版本中作为新功能发布,Java的发展就是通过JCP来推动)。
JAX-RS API概况
JAX-RS定义的包结构如下,包含近五十多个接口,注解和抽象类:
javax.ws.rs:包含用于创建RESTful服务资源的高层次(High-level)接口和注解;
javax.ws.rs.core:包含用于创建RESTful服务资源的低层次(Low-level)接口和注解;
javax.ws.rs.ext:包含用于扩展JAX-RS API支持类型的APIs;
注意:JAX-RS规范只是定义API,真正开发RESTful Web服务需要引入具体实现,具体实现由第三方提供,如Sun的参考实现Jersey,Apache CXF,Jboss RESTEasy;
JAX-RS API的目标
-
基于POJO:该API提供一组注解,类,接口用于将POJOs暴露为网络资源(Web Resource),并定义对象的生命周期和有效范围;
-
以HTTP为中心:该规范将默认HTTP作为底层网络协议并提供一个清晰的HTTP和URI元素到API类和注解之间的映射,该API提供对常见的HTTP使用模式和包括WebDAV,Atom发布协议在内的各种HTTP应用进行支持;
-
格式无关性:该API将能处理多种不同内容格式的HTTP实体,并提供统一的扩展机制允许应用新增支持其他内容格式(通过实体Provider);
-
容器无关性:使用该API的应用能被部署到多个Web容器,该规范将定义如何将应用部署在Servlet容器和作为一个JAX-WS提供者;
-
融入Java EE:该规范将定义在Java EE容器中网络资源(Web Resource)类的环境并将指导如何使用Java EE提供的功能和组件;
如下不属于JAX-RS API的目标:
-
支持J2SE5.0之前的版本:该API扩展并使用了注解功能,需要J2SE5.0或更高版本;
-
描述,注册和发现:该规范未定义也不需要任何服务描述,注册和发现功能;
-
客户端API:该规范未定义客户端APIs,期望其他规范提供该功能;
-
HTTP栈:该规范未定义一个新HTTP栈,HTTP协议支持是通过部署使用该API应用的容器;
-
数据模型/格式类:该规范未定义类用于操作实体内容,而是提供一种扩展机制允许使用该API的应用定义使用这些类;
JAX-RS主要接口/类介绍
应用(Application)
一个JAX-RS应用包含一个或多个资源(Resource)类和零个或多个Provider;组成JAX-RS应用的资源(Resource)类和Provider通过应用提供的Application子类进行配置;JAX-RS第三方实现可以提供其他机制发现资源(Resource)类和Provider(如:运行时自动扫描),但使用Application是唯一可移植的方法(各个实现必须支持);
应用(Application)的发布方式:
-
Java SE:通过基于JAX-WS的javax.xml.ws.Provider在SE环境中发布;
-
Servlet:将JAX-RS应用打包为.war文件作为Web应用发布;
资源(Resource)
在JAX-RS中,一个Resource类代表一个网络资源,对该网络资源的任何请求被Resource类中定义的方法处理,在Java中,一个Resource是一个POJO类,其中的方法至少有一个被@Path或HTTP方法指示器(如@GET,@POST,@PUT,@DELETE,@OPTIONS)标注;
1,生命周期
默认每个请求创建一个新的Resource实例来处理请求;
过程如下图所示:
图1:对资源的每个请求都会创建一个实例
注:实现可以提供支持资源类其他生命周期(如IOC容器管理资源类)
2,构造器
Root资源类是通过JAX-RS运行时初始化的,所以必须提供一个public构造器(可带参数由运行时提供,也可不带参数),非Root资源类通过应用初始化则不必如此;public构造器的参数可由:@Context,@HeaderParam,@CookieParam,@MatrixParam,@QueryParam和@PathParam进行标注,当有多个public构造器,实现需选择一个参数最多的,当有多个参数个数相同的构造器时,实现能依据自己的方式选择一个合适的,并能发出告警;
3,字段和属性
当资源类被实例化时,字段和Bean属性可以通过如下注解进行初始化赋值:
@MatrixParam:取得URI matrix参数值;
@QueryParam:取得URI query参数值;
@PathParam:取得URI template参数值;
@CookieParam:取得cookie值;
@HeaderParam:取得HTTP请求头的值;
@Context:注入其他上下文实例;
上面的注解除了@Context之外,都跟当前具体请求相关,所以如果实现提供了资源类其他生命周期应慎重将这些注解用于字段和Bean属性;
4,资源方法
资源方法是资源类中被请求方法指示器标注的方法,用来处理不同的HTTP请求,请求方法指示器是一个被@HttpMethod标注的运行时注解,JAX-RS为常用HTTP方法定义了一组请求方法指示器:@GET,@POST,@PUT,@DELETE,@HEAD,用户也可通过@HttpMethod自定义请求方法指示器;
-
可见性:只有public方法才能作为资源方法;
-
参数:资源方法参数可被@FormParam和上述用于字段的注解标注,@DefaultValue用于为参数提供一个默认值,@Encoded用于指名明不自动URI解码参数值;未被标注的参数(称为实体参数)用于映射请求的实体部分;实体部分与Java类型之间的转换是由实体Provider提供,有关Provider的信息详见下节(Provider);
-
返回类型:资源方法可以返回void,Response,GenericEntity,或其他Java类型,这些返回类型将按如下方式映射到响应实体;
-
void:返回204状态码,响应体为空;
-
Response:Response的entity属性作为响应体,status属性作为状态码;如果没有设置status,当entity为空时status为204,不为空时status为200;
-
GenericEntity:GenericEntity的Entity属性作为响应体,当返回值non-null时,状态码为200,为null时状态码为204;
-
其他Java类型:返回实例作为响应体,当返回值为non-null时,状态码为200,为null时状态码为204;
5,URI模板
Root资源类通过@Path映射到一个URI地址空间,该注解值是一个基于部署上下文(context)和应用路径(详见@ApplicationPath)组合的相对URI路径;
一个URI模板是一个包含0或多个嵌入参数的串,当所有的参数都提交后就是形成一个合法的URI;
如下所示:
@Path(“widgets/{id}”)
public class Widget{
…
}
上例中Widget资源类被相对URI路径widgets/xxx定位,其中xxx是id参数的值;
@Path的值被自动编码,如下两个注解是等价的:
@Path(“widget list/{id}”)
@Path(“widget%20list/{id}”)
模板参数可使用正则表达式来匹配值,例如:
@Path(“widgets/{path:.+}”)
public class Widget{
…
}
上例中Widget资源类将被匹配到路径始于widgets并包含至少一个路径分段(segment)的请求,path参数值是widgets/后的串,如请求路径是widgets/small/a则path参数值为small/a;
6,子资源
资源类中被@Path标注的方法要么是子资源方法(sub-resource methods),要么是子资源定位器(sub-resource locators);子资源方法能直接处理HTTP请求,而子资源定位器将返回一个对象(也可以包含子资源定位符)来处理HTTP请求;
资源方法存在或缺少请求方法指示器(designator),如有如下不同:
1,存在,这些方法(称为子资源方法)将被看作一个正常的资源方法除了能用于处理匹配的URI请求;
2,缺少,这些方法(称为子资源定位器)用于动态产生对象来处理请求,返回对象被看作资源类实例用于处理请求(通过子资源方法)或进一步产生对象来处理请求(通过子资源定位器);
通过例子说明:
@Path(“widgets”)
public class WidgetsResource {
@GET
@Path(“offers”)
public WidgetList getDiscounted() {…}
@Path(“{id}”)
public WidgetResource findWidget(@PathParam(“id”) String id) {
return new WidgetResource(id);
}
}
public class WidgetResource {
public WidgetResouce(String id) {…}
@GET
public Widget getDetails() {…}
}
上例中,对widgets/offers的GET请求直接被WidgetsResource资源类的子资源方法getDiscounted处理,而对widgets/xxx的GET请求则被WidgetResource资源类的getDetail方法处理;
7,声明媒体类型
应用可以通过@Consumes,@Produces来分别声明请求,响应所支持的媒体类型;此注解可以应用于资源方法,资源类或实体提供者(Provider);在资源方法上使用这些注解将覆盖资源类和实体提供者(Provider)对方法参数/返回类型的标注;缺省时默认支持所有媒体类型(*/*);
通过例子说明:
@Path(“widgets”)
@Produces(“application/widgets+mxl”)
public class WidgetsResouce {
@GET
Public Widgets getAsXML() {…}
@GET
@Produces(“text/html”)
Public String getAsHtml() {…}
@POST
@Consumes(“application/widgets+xml”)
Public void addWidget(Widget widget) {…}
}
@Provider
@Produces(“application/widgets+xml”)
Public class WidgetsProvider implements MessageBodyWriter<Widgets> {…}
@Provider
@Consumes(“application/widgets+xml”)
Public class WidgetProvider implements MessageBodyReader<Widget> {…}
如上:
-
getAsXML资源方法被调用于处理GET请求且响应实体媒体类型为application/widgets+xml,该方法返回的Widgets实例将被WidgetsProvider映射为application/widgets+xml类型(详见MessageBodyWriter);
-
getAsHtml资源方法被调用于处理GET请求且响应实体媒体类型为text/html,该方法返回的String包含text/html将被默认实现MessageBodyWriter<String>写回到客户端;
-
addWidget资源方法被调用于处理POST请求且请求实体媒体类型为application/widgets+xml,将通过WidgetProvider将请求实体映射为widget参数(详见MessageBodyReader);
8,注解继承
JAX-RS注解可被用于超类/接口的方法和方法参数,这些注解可被继承如果子类/实现类在对应的方法上没有任何注解,如果子类/实现类在对应的方法上有任何注解,父类/接口中该方法的注解将被忽略;
通过例子说明:
Public interface ReadOnlyAtomFeed {
@GET
@Produces(“application/atom+xml”)
Feed getFeed();
@Path(“feed”)
Public class ActivityLog implements ReadOnlyAtomFeed {
Public Feed getFeed() {…}
}
}
上例中,ActivityLog.getFeed继承了ReadOnlyAtomFeed.getFeed的@GET和@Produces注解;相反的:
@Path(“feed”)
Public class ActivityLog implements ReadOnlyAtomFeed {
@Produces(“application/atom+xml”)
Public Feed getFeed() {…}
}
上例中,ReadOnlyAtomFeed.getFeed的@GET注解未被ActivityLog.getFeed继承,由于被@Produces标注所以该方法需要自己标注请求方法指示器;
提供者(Provider)
JAX-RS运行时通过应用提供的Provider类进行扩展;Provider是一个被@Provider标注并实现了一个或多个JAX-RS接口的类;
1,生命周期
默认一个JAX-RS应用中每个Provider只有一个实例(单例,这跟Resource正好相反);(实现可支持Provider其他生命周期,如通过IOC容器管理所有Provider);
2,构造器
Provider类被JAX-RS运行时进行初始化,故而需要一个public构造器(可带参数有运行时提供,也可不带参);构造器参数可被@Context标注,因为Provider的创建超出了具体请求范围(非具体请求相关,这里注意与Resource的区别),所以在创建时只有部署相关的信息才能被注入,与具体请求相关的信息在Provider方法被调用时才可见;如果有多个public构造器,实现应选择一个参数最多的,如果有多个参数个数相同的构造器,实现应给出告警;
3,实体Provider
实体Provider用于在请求/响应实体与Java类型之间进行映射,有两种:MessageBodyReader(请求实体映射到Java类型)和MessageBodyWriter(Java类型映射到响应实体);
-
Message Body Reader
MessageBodyReader接口定义了JAX-RS运行时与那些提供了将实体映射到Java类型功能的组件之间的关系,一个提供该功能的类需要实现MessageBodyReader接口并被@Provider标注;
JAX-RS实现处理请求实体到Java方法参数映射的逻辑步骤如下:
1,取得请求的媒体类型,如果请求中没有包含Content-Type请求头则使用application/octet-stream;
2,确定参数的Java类型,该类型能映射处理请求实体;
3,取得能支持请求媒体类型的MessageBodyReader Provider类集合;
4,遍历MessageBodyReader类集合,调用isReadable方法选择支持期望的Java类型的Provider类;
5,如果在第4步中确定了一个合适的MessageBodyReader类则调用readFrom方法将请求实体映射为期望的Java类型对象;
6,否则通过JavaBeans Activation Framework查找一个合适的数据处理器将请求实体映射为期望的Java类型;
7,否则生成WebApplicationException,包含一个不支持媒体类型的响应(415)和空实体;
-
Message Body Writer
MessageBodyWriter接口定义了JAX-RS运行时与那些提供了将Java类型映射到实体功能的组件之间的关系,一个提供该功能的类需要实现MessageBodyWriter接口并被@Provider标注;
JAX-RS实现处理Java返回类型到响应实体的逻辑步骤如下:
1,取得将被映射为响应体的对象,如果返回类型是Response或其子类,则取entity属性的值,其他类型则取返回对象的值;
2,确定响应实体的媒体类型;
3,取得能支持响应媒体类型的MessageBodyWriter Provider类集合;
4,对类型的相近度对MessageBodyWriter进行排序;
5,遍历MessageBodyWriter类集合,调用isWriteable方法选择支持将Java对象映射为响应实体的Provider类;
5,如果在第4步中确定了一个合适的MessageBodyWriter类则调用writeTo方法将Java对象映射为响应实体;
6,否则通过JavaBeans Activation Framework查找一个合适的数据处理器将Java对象映射为响应实体;
7,否则生成WebApplicationException,包含一个服务器内部错误响应(500)和空包体;
-
声明媒体类型
消息体Reader和Writer可通过@Consume,@Produces来限制他们支持的媒体类型,缺省时默认支持所有媒体类型(*/*);在选择Provider时实现应能按照它们支持的媒体类型对其进行排序,媒体类型的常见排序规则:x/y < x/* < */*;
-
标准实体Provider
实现需要提供常用的MessageBodyReader和MessageBodyWriter,具体支持的Java类型与媒体类型组合如下:
Byte[] */*
Java.lang.String */*
Java.io.InputStream */*
Java.io.Reader */*
Java.io.File */*
Javax.activation.DataSource */*
4,上下文(Context)Provider
用于向资源类和其他Provider提供上下文;一个上下文Provider类需实现ContextResolver<T>接口并被@Provider标注,例如要为JAXB实体Provider提供一个自定义的JAXBContext,需要类实现ContextResolver<JAXBContext>接口;
-
声明媒体类型
上下文Provider可通过@Consume,@Produces来限制他们支持的媒体类型,缺省时默认支持所有媒体类型(*/*);在选择Provider时实现应能按照它们支持的媒体类型对其进行排序,媒体类型的常见排序规则:x/y < x/* < */*;
5,异常映射Provider
当一个资源或Provider方法抛出异常时,JAX-RS运行时应能将异常映射为一个合适的HTTP响应,应用可以通过异常映射Provider对这种映射进行定制,异常映射Provider将一个检查的或运行时异常映射为一个Response实例;一个异常映射Provider类需实现ExceptionMapper<T>接口并被@Provider标注;
上下文(Context)
1,并发
上下文(Context)对应于具体请求而JAX-SR组件(Providers和那些非单例的资源类)可能需要支持多个并发请求,当注入一个上下文时,组件实例应能为具体的请求选择正确的上下文(Context),使用thread-local是个常见的方法(如Tomcat,Spring内部实现使用ThreadLoacl);
Context类型:
2,Application
一个应用提供的Application子类可通过@Context注入到其他类的字段和方法参数中;通过该Application子类来统一访问配置信息;
3,URI模板
一个UriInfo实例可通过@Context注入到类的属性和方法参数中;UriInfo提供每个请求关于URI组成元素的静态和动态信息;
下面的例子展示了取得请求的所有查询(query
)参数:
@GET
@Produces{“text/plain”}
Public String listQueryParam(@Context UriInfo info) {
StringBuilder buf = new StringBuilder();
for(String param: info.getQueryParameters().keySet()) {
buf.append(param);
buf.append(“\n”);
}
return buf.toString();
}
4,Header
一个HttpHeaders实例可通过@Context注入类的属性和方法参数中;HttpHeaders提供访问请求头信息;
下面的例子展示了取得请求的所有请求头信息:
@GET
@Produces{“text/plain”}
Public String listHeaderNames(@Context HttpHeader headers) {
StringBuilder buf = new StringBuilder();
for(String param: header.getRequestHeaders().keySet) {
buf.append(header);
buf.append(“\n”);
}
return buf.toString();
}
5,内容协商与前置条件
JAX-RS通过Request接口支持内容协商和前置条件,一个Request实例通过@Context注入到类的属性和方法参数中。
下面的例子展示了在更新资源之前检查当前实体TAG
是否与前置条件匹配:
@PUT
Public Response updateFoo(@Context Request request, Foo foo) {
EntityTag tag = getCurrentTag();
ResponseBuilder builder = request.evaluatePreconditions(tag);
if(builder != null) return builder.bulid();
eblse return doUpdate(foo);
}
6,安全上下文
SecurityContext接口提供访问当前请求的安全上下文,一个SecurityContext实例通过@Context注入到类的字段和方法参数中,通过SecurityContext可访问到用户,角色,授权信息。
7,Providers
Providers接口能基于一组查询标准查找到Provider实例;一个Providers实例能通过@Context注入到类的字段和方法参数中,该接口被Provider用于取得并使用其他Provider提供的功能;
环境(Environment)
哪些容器管理的资源能被JAX-RS的Root资源类和Provider使用取决于其部署的环境;
-
Servlet容器:基于Servlet的JAX-RS实现支持将JAX-RS应用部署到Servlet容器,并能将Servlet相关信息注入到应用的Resource和Provider类中:ServletConfig,ServletContext,HttpServletRequest,HttpServletResponse;
-
JEE容器:基于JEE容器的JAX-RS实现应支持能将EJB,Managed Beans等作为Root资源类,Provider和Application子类;
总结
本文主要依据JAX-RS1.1规范的英文版翻译而成,跟规范不同的是该文主要讲述JAX-RS的编程元素(针对应用开发者而非规范实现者)如:Application及其子类,Resource,Provider,Context而非如何实现规范并加入自己的理解;整个JAX-RS1.1英文原版规范共51页,有兴趣的朋友可以下载详细研究;