2.4 连通性
REST的一个重要的特性就是连通性。Web Link和HATEOAS以不同方式实现了REST式服务的联通性。
Web Link定义在IETFRFC 5988(Web Linking),是通过在HTTP头中定义链接信息,以描述当前页面与链接页面之间的关系。Web Link是一种过渡型链接(Transitional Links)。JAX-RS 2.0引入了javax.ws.rs.core.Link类,用来处理Web Link的表述。
HATEOAS(Hypermedia as the Engine of Application State,超媒体作为应用程序状态引擎)。HATEOAS的形式是包含链接信息的超媒体文档,HATEOAS的核心是“引擎”,该引擎的目的是通过请求的响应实体将超媒体信息返回给客户端,超媒体信息可以告诉用户,如果接下来选择去往某个链接(或者链接列表中的某个链接),应用的状态就会如超媒体描述的那样发生转变。HATEOAS是一种结构型链接(Structural Links)。Jersey2中可以使用XML实现HATEOAS的结构要求。
下面讲述在Jersey中如何实现Web Link和HATEOA这两种REST连通性实践方式。
阅读指南
本节示例源代码地址:https://github.com/feuyeux/jax-rs2-guide-II/tree/master/2.simple-service-3。
相关包:com.example.link。
2.4.1 过渡型链接
Web Link通过使用HTTP的头信息来传递操作链接,在Jersey中使用javax.ws.rs.core.Link类可以非常简洁地实现支持Web Link的资源类,示例代码如下。
@Path("weblink-resource")
public class WebLinkResource {
@Context
UriInfo uriInfo;
@POST
@Produces(MediaType.APPLICATION_XML)
@Consumes({ MediaType.APPLICATION_JSON,
MediaType.APPLICATION_XML, MediaType.TEXT_XML })
public Response saveBook(final Book book) {
final long newId = System.nanoTime();
book.setBookId(newId);
LinkCache.map.put(newId, book);
//关注点1:通过UriInfo实例获取资源路径
final UriBuilder ub = uriInfo.getAbsolutePathBuilder();
final URI location = ub.path("" + newId).build();
//关注点2:通过模板获取资源路径
final String uriTemplate = "http://{host}:{port}/{path}/{param}";
final URI location2 = UriBuilder.fromUri(uriTemplate)
.resolveTemplate("host", "localhost").resolveTemplate("port", "9998")
.resolveTemplate("path", "weblink-resource")
.resolveTemplate("param", newId).build();
//关注点3:通过模板方法获取资源路径
final UriBuilder ub3 = uriInfo.getAbsolutePathBuilder();
final URI location3 = ub3.scheme("http").host("localhost").port(9998)
.path("weblink-resource").path("" + newId).build();
//关注点4:为响应实例添加路径信息
return Response.created(location).link(location2, "view1")
.link(location3, "view2").entity(book).build();
}
}
在这段代码中,使用了3种方式构建URI实例。第一种方式是通过调用UriInfo实例的getAbsolutePathBuilder()方法可以获取当前请求的绝对路径,然后基于此路径添加资源id信息,见关注点1;第二种方式是为UriBuilder提供路径模板,然后链式调用resolveTemplate()方法传递并解析模板参数,最后通过UriBuilder的build方法生成URI实例,见关注点2;第三种方式和第二种类似,不同的是模板信息被具体方法替代。最后,这3个与Link相关的URI实例由Response构建,作为返回值响应给客户端,见关注点4。
2.4.2 结构型链接
HATEOAS用以代替聚集数据并避免描述膨胀,通常使用 Atom格式在实体字段中提供链接信息。本例使用XML格式来支持HATEOAS,折中的设计是在POJO中额外定义一个链接字段。支持HATEOAS的资源类示例如下。
@Path("hateoas-resource")
public class HATEOASResource {
@Context
UriInfo uriInfo;
@POST
@Produces({ MediaType.APPLICATION_XML })
@Consumes({ MediaType.APPLICATION_XML })
public BookWrapper saveBook(final Book book) {
final long newId = System.nanoTime();
book.setBookId(newId);
LinkCache.map.put(newId, book);
//关注点1:通过UriInfo实例获取资源路径
final UriBuilder ub = uriInfo.getAbsolutePathBuilder();
final URI uri = ub.path("" + newId).build();
BookWrapper b = new BookWrapper();
b.setBook(book);
//关注点2:将资源路径赋给资源实体
b.setLink(uri.toString());
return b;
}
}
在这段代码中,URI实例由上下文UriInfo中获取的绝对路径和资源ID组成,见关注点1;该链接信息被赋值到POJO实例的link属性中,以实现HATEOAS,见关注点2。
阅读指南
REST连通性的实践手段非常多,推荐读者从成熟的产品中学习其设计。如果有可能,这里推荐Jenkins和RallyDev两个敏捷开发中常用的平台,它们提供了比较舒适的连通性设计。比如在RallyDev中,为一个测试用例结果添加测试用例属性(该属性是必输项),其内容并不是对应测试用例的实例,而是该测试用例的引用地址字符串。这样的设计不但减少了网络传输的负载,还方便在调试和维护时排错。