NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

Java RESTful Web Service 实战

  • 2016-08-20
  • 本文字数:11715 字

    阅读完需:约 38 分钟

编者按: InfoQ 开设栏目“品味书香”,精选技术书籍的精彩章节,以及分享看完书留下的思考和收获,欢迎大家关注。本文节选自韩陆著《Java RESTful Web Service 实战》中的章节“REST API 设计”,介绍REST 请求流程等内容。

3.3 REST 请求流程

从 REST 请求处理的扩展点上,我们已经讲述了用于实体处理的 Provider 接口以及上下文处理和异常处理的 Provider。本章还将讲述两种在面向切面编程中非常重要的特殊 Provider:过滤器(3.4 节)和拦截器(3.5 节)。在进入这个主题之前,我们需要对 REST 请求处理的流程这条线有明确的认识,如图 3-5 所示,这样以来,才会知道这些点都处于流程的什么位置。只有这样才能清楚地实现对扩展点的开发和调试。

在图 3-5 中,请求流程中存在 3 种角色,分别是用户、REST 客户端和 REST 服务器。请求始于请求的发送,止于调用 Response 类的 readeEntity() 方法,获取响应实体。

1)用户提交请求数据,客户端接收请求,进入第一个扩展点:“客户端请求过滤器 ClientRequestFilter 实现类”的 filter() 方法。

2)请求过滤处理完毕后,流程进入第二个扩展点:“客户端写拦截器 WriterInterceptor 实现类”的 aroundWriteTo() 方法,实现对客户端序列化操作的拦截。

3)“客户端消息体写处理器 MessageBodyWriter”执行序列化,流程从客户端过渡到服务器端。

4)服务器接收请求,流程进入第三个扩展点:“服务器前置请求过滤器 Container-RequestFilter 实现类”的 filter() 方法。

图 3-5 Jersey 的 REST 请求处理流程

5)过滤处理完毕后,服务器根据请求匹配资源方法,如果匹配到相应的资源方法,流程进入第四个扩展点:“服务器后置请求过滤器 ContainerRequestFilter 实现类”的 filter() 方法。

6)后置请求过滤处理完毕后,流程进入第五个扩展点:“服务器读拦截器 ReaderInterceptor 实现类”的 aroundReadFrom() 方法,拦截服务器端反序列化操作。

7)“服务器消息体读处理器 MessageBodyReader”完成对客户端数据流的反序列化。服务器执行匹配的资源方法。

8)REST 请求资源的处理完毕后,流程进入第六个扩展点:“服务器响应过滤器 ContainerResponseFilter 实现类”的 filter() 方法。

9)过滤处理完毕后,流程进入第七个扩展点:“服务器写拦截器 WriterInterceptor 实现类”的 aroundWriteTo() 方法,对服务器端序列化到客户端这个操作的拦截。

10)“服务器消息体写处理器 MessageBodyWriter”执行序列化,流程返回到客户端一侧。

11)客户端接收响应,流程进入第八个扩展点:“客户端响应过滤器 Client-ResponseFilter 实现类”的 filter() 方法。

12)过滤处理完毕后,客户端响应实例 response 返回到用户一侧,用户执行 response.readEntity() 流程进入第九个扩展点:“客户端读拦截器 ReaderInterceptor 实现类”的 aroundReadFrom() 方法,对客户端反序列化进行拦截。

13)“客户端消息体读处理器 MessageBodyReader”执行反序列化,将 Java 类型的对象最终作为 readEntity() 方法的返回值。到此,一次 REST 请求处理的完整流程完毕。这期间,如果出现异常或资源不匹配等情况,会从出错点开始结束流程。

3.4 REST 过滤器

从上一节的流程讲述中,我们了解 JAX-RS2 定义的 4 种过滤器扩展点(Extension Point)接口,供开发者实现业务逻辑,按请求处理流程的先后顺序为:客户端请求过滤器(ClientRequestFilter)-> 服务器请求过滤器(ContainerRequestFilter)-> 服务器响应过滤器(ContainerResponseFilter)-> 客户端响应过滤器(ClientResponseFilter)。本节将全面讲述 4 种过滤器的使用。

3.4.1 ClientRequestFilter

客户端请求过滤器(ClientRequestFilter)定义的过滤方法 filter() 包含一个输入参数,是客户端请求的上下文类 ClientRequestContext。从该上下文中可以获取请求信息,典型的示例包括获取请求方法 context.getMethod(),获取请求资源地址 context.getUri() 和获取请求头信息 context.getHeaders() 等。过滤器的实现类中可以利用这些信息,覆写该方法以实现该类特有的过滤功能。ClientRequestFilter 接口的实现类如图 3-6 所示。

图 3-6 ClientRequestFilter 接口的实现类

图 3-6 展所示了 ClientRequestFilter 接口的实现类,包括 Jersey 内部提供的实现类和本书示例代码中的实现类。我们选择 HTTP 认证过滤器类 HttpAuthenticationFilter 作为例子,来感受上面的讲述,(HTTP 基本认证的内容请参考 10.1 节。)示例代码如下所示。

复制代码
@Override
public void filter(ClientRequestContext request) throws IOException {
if(!"true".equals(request.getProperty("org.glassfish.jersey.client.authentication.HttpAuthenticationFilter.reused"))) {
if(!request.getHeaders().containsKey("Authorization")) {
HttpAuthenticationFilter.Type operation = null;
if(this.mode == Mode.BASIC_PREEMPTIVE) {
this.basicAuth.filterRequest(request);
operation = HttpAuthenticationFilter.Type.BASIC;
...
if(operation != null) {
request.setProperty("org.glassfish.jersey.client.authentication.HttpAuthenticationFilter.operation", operation);
}

在这段代码中,HTTP 基本认证过滤器类在 filter() 方法中,判断请求头信息中是否包含"Authorization",如果不包含则通过 filterRequest() 方法添加请求头"Authorization"为 authentication,authentication 的内容是"Basic" + Base64.encodeAsString(usernamePassword)。这样以来,经过 HTTP 基本认证过滤器类过滤处理后,可以确保请求头信息中包含"Authorization"。

3.4.2 ContainerRequestFilter

针对过滤切面,服务器请求过滤器接口 ContainerRequestFilter 的实现类可以定义为预处理和后处理。默认情况下,采用后处理方式。即先执行容器接收请求操作,当服务器接收并处理请求后,流程才进入过滤器实现类的 filter() 方法。而预处理是在服务器处理接收到的请求之前就执行过滤。如果希望实现一个预处理的过滤器实现类,需要在类名上定义注解 @PreMatching。

服务器请求过滤器定义的过滤方法 filter() 包含一个输入参数,即容器请求上下文类 ContainerRequestContext。ContainerRequestFilter 接口的实现类,如图 3-7 所示。

图 3-7 ContainerRequestFilter 接口的实现类

图 3-7 展示了 ContainerRequestFilter 接口的实现类,我们以 CsrfProtectionFilter 为例来说明,示例代码如下所示。

复制代码
package org.glassfish.jersey.server.filter;
@Priority(Priorities.AUTHENTICATION) //post-matching
public class CsrfProtectionFilter implements ContainerRequestFilter {
public static final String HEADER_NAME = "X-Requested-By";
// 关注点 1 忽略方法集合
private static final Set<String> METHODS_TO_IGNORE;
static {
HashSet<String> mti = new HashSet<>();
mti.add("GET");
mti.add("OPTIONS");
mti.add("HEAD");
METHODS_TO_IGNORE = Collections.unmodifiableSet(mti);
}
@Override
public void filter(ContainerRequestContext rc) throws IOException {
// 关注点 2 判断方法名称是否符合条件
if (!METHODS_TO_IGNORE.contains(rc.getMethod()) && !rc.getHeaders().containsKey(HEADER_NAME)) {
throw new BadRequestException();
}
}
}

在这段代码中,CsrfProtectionFilter 定义了一个特殊的头信息"X-Requested-By"和 CSRF 忽略监控的方法集合,见关注点 1。在过滤器的 filter() 方法中,首先从上下文中获取头信息 rc.getHeaders() 和请求方法信息 rc.getMethod(),然后判断头信息是否包含"X-Requested-By",方法信息是否是安全的请求方法,即"GET"、“OPTIONS"或"HEAD”。如果两个条件都不成立,过滤器会抛出一个运行时异常 BadRequestException,见关注点 2。通过 CsrfProtectionFilter 过滤器,可以确保请求是 CSRF 安全的。

阅读指南

CsrfProtectionFilter 类使用了注解 @Priority(Priorities.AUTHENTICATION) 来定义该类,明确了该过滤器具有最高的优先级。同时,以注解的文字告诉开发者,需要将其放在过滤器链的第一个位置。因此,在定义和使用过滤器时,需要考虑运行中过滤器的执行先后顺序,否则无法实现过滤器的功能或者使流程混乱。

3.4.3 ContainerResponseFilter

服务器响应过滤器接口 ContainerResponseFilter 定义的过滤方法 filter() 包含两个输入参数,一个是容器请求上下文类 ContainerRequestContext,另一个是容器响应上下文类 ContainerResponseContext。ContainerResponseFilter 接口的实现类如图 3-8 所示。

图 3-8 ContainerResponseFilter 接口的实现类

图 3-8 展示了 ContainerResponseFilter 接口的实现类,我们以 EncodingFilter 为例来说明。该过滤器的作用是完成内容协商中编码匹配的工作(内容协商这个知识点请参考 3.6 节),示例代码如下所示。

复制代码
@Priority(Priorities.HEADER_DECORATOR)
public final class EncodingFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext request, ContainerResponseContext response) throws IOException {
...
List<String> varyHeader = ((ContainerResponse) response).getStringHeaders().get(HttpHeaders.VARY);
// 关注点 1:Vary 头信息
if (varyHeader == null || !varyHeader.contains(HttpHeaders.ACCEPT_ENCODING)) {
response.getHeaders().add(HttpHeaders.VARY, HttpHeaders.ACCEPT_ENCODING);
}
...
// 关注点 1:Content-Encoding 头信息
if (!IDENTITY_ENCODING.equals(contentEncoding)) {
response.getHeaders().putSingle(HttpHeaders.CONTENT_ENCODING, contentEncoding);
}
}

EncodingFilter 过滤器的 filter() 方法通过对请求头信息“Accept-Encoding”的分析,先后为响应头信息“Vary”和“Content-Encoding”赋值,以实现编码部分的内容协商。见关注点 1。

3.4.4 ClientResponseFilter

客户端响应过滤器(ClientResponseFilter)定义的过滤方法 filter() 包含两个输入参数,一个是客户端请求的上下文类 ClientRequestContext,另一个是客户端响应的上下文类 ClientResponseContext。ClientResponseFilter 接口的实现类,如图 3-9 所示。

图 3-9 ClientResponseFilter 接口的实现类

图 3-9 展示了 ClientResponseFilter 接口的实现类,包括 Jersey 内部提供的实现类和本书示例代码中的实现类。我们以 HTTP 摘要认证过滤器类 HttpAuthenticationFilter 为例进行演示,(HTTP 摘要认证请参考 10.1 节。)示例代码如下所示。

复制代码
@Override
public void filter(ClientRequestContext request, ClientResponseContext response) throws IOException {
...
if(response.getStatus() == Status.UNAUTHORIZED.getStatusCode()) {
String operation = (String)response.getHeaders().getFirst("WWW-Authenticate");
if(operation != null) {
String success = operation.trim().toUpperCase();
if(success.startsWith("BASIC")) {
result = HttpAuthenticationFilter.Type.BASIC;
} else {
if(!success.startsWith("DIGEST")) {
return;
}
result = HttpAuthenticationFilter.Type.DIGEST;
}
}
authenticate = true;
} else {
authenticate = false;
}
if(this.mode != Mode.BASIC_PREEMPTIVE) {
if(this.mode == Mode.BASIC_NON_PREEMPTIVE) {
if(authenticate && result == HttpAuthenticationFilter.Type.BASIC) {
this.basicAuth.filterResponseAndAuthenticate(request, response);
}
} else if(this.mode == Mode.DIGEST) {
if(authenticate && result == HttpAuthenticationFilter.Type.DIGEST) {
this.digestAuth.filterResponse(request, response);
}
}
...
}

3.4.5 访问日志

3.4.1〜3.4.5 节完成了对 JAX-RS2 定义的 4 种过滤器的讲述,本节利用上述知识,演示如何综合运用过滤器,完成一个记录 REST 请求的访问日志。

1. 访问日志实现类

访问日志类 AirLogFilter 实现了上述的 4 种过滤器,旨在记录服务器和客户端的请求和响应运行时的信息。AirLogFilter 类定义如下所示。

复制代码
@PreMatching
public class AirLogFilter implements ContainerRequestFilter, ClientRequestFilter,
ContainerResponseFilter, ClientResponseFilter {

AirLogFilter 为每一种过滤器接口定义的 filter() 方法提供了实现。在客户端请求过滤中,输出请求资源地址信息和请求头信息;在容器请求过滤中,输出请求方法、请求资源地址信息和请求头信息;在容器响应过滤中,输出 HTTP 状态码和请求头信息;在客户端响应过滤中,输出 HTTP 状态码和请求头信息。4 个阶段的 filter() 示例代码如下所示。

复制代码
@Override
public void filter(ClientRequestContext context) throws
IOException {
long id = logSequence.incrementAndGet();
StringBuilder b = new StringBuilder();
// 关注点 1:获取请求方法和地址
printRequestLine(CLIENT_REQUEST, b, id, context.getMethod(), context.getUri());
// 关注点 2:获取请求头信息
printPrefixedHeaders(CLIENT_REQUEST, b, id, HeadersFactory.asStringHeaders (context.getHeaders()));
LOGGER.info(b.toString()); }

在这段代码中,AirLogFilter 类实现了客户端响应过滤。从客户端响应上下文实例中,可以获取到响应状态信息和响应头信息。分别见关注点 1 和关注点 2。

复制代码
@Override
public void filter(ContainerRequestContext context) throws IOException {
long id = logSequence.incrementAndGet();
StringBuilder b = new StringBuilder();
// 关注点 1:获取容器请求方法和请求地址信息
printRequestLine(SERVER_REQUEST, b, id, context.getMethod(), context.getUriInfo().getRequestUri());
// 关注点 2:获取请求头信息
printPrefixedHeaders(SERVER_REQUEST, b, id, context.getHeaders());
LOGGER.info(b.toString());
}

在这段代码中,AirLogFilter 类实现了容器请求过滤。从容器请求上下文实例中,可以获取到请求方法和请求资源地址信息,见关注点 1。同样,可以从中获取请求头信息,见关注点 2。

复制代码
@Override
public void filter(ContainerRequestContext requestContext,
ContainerResponseContext responseContext) throws IOException {
long id = logSequence.incrementAndGet();
StringBuilder b = new StringBuilder();
// 关注点 1:获取容器响应状态
printResponseLine(SERVER_RESPONSE, b, id, responseContext.getStatus());
// 关注点 2:获取容器响应头信息
printPrefixedHeaders(SERVER_RESPONSE, b, id, HeadersFactory.asStringHeaders(responseContext.getHeaders()));
LOGGER.info(b.toString());
}

在这段代码中,AirLogFilter 类实现了容器响应过滤。从容器响应上下文实例中,可以获取到容器的响应状态信息和响应头信息。分别见关注点 1 和关注点 2。 2. 单元测试类

访问日志的单元测试示例如下所示。

复制代码
public class TIResourceJtfTest extends JerseyTest {
@Override
protected Application configure() {
ResourceConfig config = new ResourceConfig(BookResource.class);
return config.register(com.example.filter.log.AirLogFilter.class);
}
@Override
protected void configureClient(ClientConfig config) {
config.register(new AirLogFilter());
}

在这段代码中,为了使访问日志类生效,需要测试类 TIResourceJtfTest 在 Jersey 测试框架的服务器端和客户端,分别注册服务日志类 AirLogFilter。 单元测试的结果如下所示,在 4 种过滤器中分别打印了该阶段的日志信息。

复制代码
main - 1 * AirLog - Request received on thread main
1 / GET http://localhost:9998/books/1
1 / Accept: application/json
Grizzly-worker(1) - 2 * AirLog - Request received on thread Grizzly-worker(1)
2 > GET http://localhost:9998/books/1
2 > accept: application/json
...
Grizzly-worker(1) - 3 * AirLog - Response received on thread Grizzly-worker(1)
3 < 200
3 < Content-Type: application/json
main - 4 * AirLog - Response received on thread main
4 \ 200
4 \ Content-Type: application/json
...

3.5 REST 拦截器

拦截器和过滤器的相同点是都是一种在请求—响应模型中,用做切面处理的 Provider。两者的不同除了功能性上的差异(一个用于过滤消息,一个用于拦截处理)之外,形式上也不同。拦截器通常读写成对,而且没有服务器端和客户端的区分。Jersey 提供的拦截器类,如图 3-10 所示。

图 3-10 读写拦截器的实现类

在图 3-10 中,Jersey 内部实现了几个典型应用的拦截器,它们是成对出现的。比如 GZiPEncoder 同时实现了读写拦截器,以实现使用 GZip 压缩格式压缩消息体的功能。

1. ReaderInterceptor

读拦截器接口 ReaderInterceptor 定义的拦截方法是 aroundReadFrom(),该方法包含一个输入参数,即读拦截器的上下文接口 ReaderInterceptorContext,从中可以获取头信息、输入流以及父接口 InterceptorContext 提供的媒体类型等上下文信息。接口方法示例如下。

复制代码
public Object aroundReadFrom(ReaderInterceptorContext context) throws java.io.IOException, javax.ws.rs.WebApplicationException;

2. WriterInterceptor

写拦截器接口 WriterInterceptor 定义的拦截方法是 aroundWriteTo(),该方法包含一个输入参数,写拦截器上下文接口 WriterInterceptorContext,从中可以获取头信息、输出流以及父接口 InterceptorContext 提供的媒体类型等上下文信息。接口方法示例如下所示。

复制代码
void aroundWriteTo(WriterInterceptorContext context) throws java.io.IOException, javax.ws.rs.WebApplicationException;

3. 编解码约束拦截器

编解码约束拦截器类 ContentEncoder 是一个位于 org.glassfish.jersey.spi 包中的拦截器,SPI 包下的工具是可插拔的。ContentEncoder 拦截器用于约束序列化和反序列化的过程中,编解码的内容协商,示例代码如下所示。

复制代码
@Override
public final Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
String contentEncoding = context.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING);
// 关注点 1:判断是否包含 Content-Encoding 头信息
if (contentEncoding != null && getSupportedEncodings().contains(contentEncoding)) {
// 关注点 2:解码处理
context.setInputStream(decode(contentEncoding, context.getInputStream()));
}
// 关注点 3:继续拦截链的下一个拦截处理
return context.proceed();
}
@Override
public final void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
String contentEncoding = (String) context.getHeaders().getFirst (HttpHeaders.CONTENT_ENCODING);
// 关注点 1:判断是否包含 Content-Encoding 头信息
if (contentEncoding != null && getSupportedEncodings().contains (contentEncoding)){
// 关注点 2:编码处理
context.setOutputStream(encode(contentEncoding, context.getOutputStream()));
}
// 关注点 3:继续拦截链的下一个拦截处理
context.proceed();
}

在这段代码中,分别给出了 ContentEncoder 拦截器的读、写拦截处理,只有当头信息包含“Content-Encoding”信息,编解码才被执行,见关注点 1。读取阶段进行解码,写入阶段进行编码,见关注点 2。上下文的 proceed() 方法用于执行拦截器链的下一个拦截器,见关注点 3。

3.6 绑定机制

在我们了解了面向切面的 Providers 的功能后,需要掌握它们是如何加载的,以及其作用域。这些容器级别的 Providers,通常使用编码的方式注册到 Application 中,但这不是唯一的办法。本节将详细讨论 Providers 的绑定机制。 默认情况下,过滤器和拦截器都是全局绑定的。也就是说,如下之一的过滤器或拦截器是全局有效的。

  • 通过手动注册到 Application 或者 Configuration;
  • 注解为 @Provider,被自动探测。

下面介绍其他的绑定机制。

3.6.1 名称绑定

过滤器或拦截器可以使用特定的注解来指定其作用范围,这种特定的注解被称为名称绑定。

1. 名称绑定注解

使用 @NameBinding 注解可以定义一个运行时的自定义注解,该注解用于定义类级别名称和类的方法名,代码示例如下所示。

复制代码
@NameBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(value = RetentionPolicy.RUNTIME)
public @interface AirLog {
}

在这段代码中,自定义注解 AirLog 使用了 @NameBinding,在运行时该注解将被解析为一个名称绑定的注解。

2. 绑定 Provider

在定义了 @AirLog 注解后,既可以在 Provider 中使用该注解,示例代码如下所示。

复制代码
// 关注点 1:使用自定义注解 @AirLog
@AirLog
@Priority(Priorities.USER)
public class AirNameBindingFilter implements ContainerRequestFilter, ContainerResponseFilter {
private static final Logger LOGGER = Logger.getLogger(AirNameBindingFilter.class);
public AirNameBindingFilter() {
LOGGER.info("Air-NameBinding-Filter initialized");
}
@Override
// 关注点 2:filter 实现访问日志
public void filter(final ContainerRequestContext containerRequest) throws IOException {
LOGGER.debug("Air-NameBinding-ContainerRequestFilter invoked:" +
containerRequest.getMethod());
LOGGER.debug(containerRequest.getUriInfo().getRequestUri());
}
@Override
// 关注点 3:filter 实现访问日志
public void filter(ContainerRequestContext containerRequest,
ContainerResponseContext responseContext) throws IOException {
LOGGER.debug("Air-NameBinding-ContainerResponseFilter invoked:" +
containerRequest.getMethod());
LOGGER.debug("status=" + responseContext.getStatus());
}
}

在这段代码中,过滤器类 AirNameBindingFilter 使用了自定义注解 @AirLog,这样 AirNameBindingFilter 类就实现了名称绑定,见关注点 1。该类实现了容器的请求和响应过滤器接口,功能是记录访问日志,见关注点 2。

3. 绑定方法

接下来,我们在资源方法级别使用自定义注解 @AirLog,来实现在资源类的指定方法上启用 AirNameBindingFilter 过滤器,示例代码如下所示。

复制代码
@Path("books")
public class BookResource {
// 关注点 1:绑定方法
@AirLog
@GET
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
public Books getBooks() {
...
return books;
}
...

在这段代码中,资源类 BookResource 包含多个方法,我们只在 getBooks() 方法上使用了注解 @AirLog,而其他方法并没有绑定,见关注点 1。

4. 单元测试类

接下来,通过单元测试来校验名称绑定的设计和实现是否正确,示例代码如下所示。

复制代码
public class TestNamingBinding extends JerseyTest {
@Override
protected Application configure() {
// 关注点 1:AirAopConfig 内部注册了 AirNameBindingFilter
return new AirAopConfig();
}
@Test
// 关注点 2:测试 getBookByPath() 方法
public void testPathGetJSON() {
final WebTarget pathTarget = target(BASE_URI).path("1");
final Invocation.Builder invocationBuilder = pathTarget.request (MediaType.APPLICATION_JSON_TYPE);
final Book result = invocationBuilder.get(Book.class);
Assert.assertNotNull(result.getBookId());
}
@Test
// 关注点 3:测试 getBooks() 方法
public void testGetAll() {
final Invocation.Builder invocationBuilder = target(BASE_URI).request();
final Books result = invocationBuilder.get(Books.class);
Assert.assertNotNull(result.getBookList());
}
}

在这段代码中,测试类 TestNamingBinding 通过 AirAopConfig 注册了 AirNameBindingFilter 过滤器,见关注点 1。该类包含两个测试方法,分别是测试资源类 BookResource 的 getBooks() 和 getBookByPath() 两个方法,见关注点 2 和关注点 3。

我们可以从终端打印的信息来检验名称绑定的运行结果,示例如下。

复制代码
Air-NameBinding-ContainerRequestFilter invoked:GET
http://localhost:9998/books/
Air-NameBinding-ContainerResponseFilter invoked:GET
status=200

从上述测试结果中可以看到,只有在 testGetAll() 方法输出的日志中输出了 AirNameBindingFilter 类中定义的日志信息。这和预期的“只有使用注解 @AirLog 定义的方法,才会在请求流程中启用相应的 Provider”一致。

3.6.2 动态绑定

名称绑定需要通过自定义的注解名称来绑定 Provider 和扩展点方法或者类,相比而言,动态绑定无须新增注解,而是使用编码的方式,实现动态特征接口 javax.ws.rs.container.DynamicFeature,定义扩展点方法的名称、请求方法类型等匹配信息。在运行期,一旦 Provider 匹配当前处理类或方法,面向切面的 Provider 方法即被触发。

1. 定义绑定 Provider

复制代码
AirDynamicFeature 类实现了 DynamicFeature 接口,示例代码如下。
public class AirDynamicFeature implements DynamicFeature {
@Override
public void configure(final ResourceInfo resourceInfo, final FeatureContext context) {
boolean classMatched = BookResource.class.isAssignableFrom(resourceInfo.getResourceClass());
boolean methodNameMatched = resourceInfo.getResourceMethod().getName().contains("getBookBy");
boolean methodTypeMatched = resourceInfo.getResourceMethod().isAnnotationPresent(POST.class);
// 关注点 1:匹配成功才注册 AirDynamicBindingFilter
if (classMatched &&(methodNameMatched || methodTypeMatched)) {
context.register(AirDynamicBindingFilter.class);
}
}
}
public class AirDynamicBindingFilter implements ContainerRequestFilter {
@Override
public void filter(final ContainerRequestContext requestContext) throws IOException {
AirDynamicBindingFilter.LOGGER.debug("Air-Dynamic-Binding-Filter invoked");
}
}

在这段代码中,在 AirDynamicFeature 的配置方法中,启用了如下匹配规则。

1)类匹配:对 BookResource 类及其子类的匹配。 2)方法名称匹配:方法名包含 getBookBy() 的匹配。 3)请求方法类型匹配:与 POST 方法的匹配。

只有当匹配成功时,才会注册 AirDynamicBindingFilter。对于 Provider 的实现类,并没有特殊的要求。

2. 单元测试类

测试类 TestDynamicBinding 注册了动态绑定特征实现类 AirDynamicFeature,示例代码如下所示。

复制代码
public class TestDynamicBinding extends JerseyTest {
@Override
protected Application configure() {
ResourceConfig config = new ResourceConfig(BookResource.class);
config.register(AirDynamicFeature.class);
return config;
}
@Test
public void testPost() {
final Book newBook = new Book("Java Restful Web Service 使用指南 -" + System.nanoTime());
final Entity<Book> bookEntity = Entity.entity(newBook, MediaType.APPLICATION_JSON_TYPE);
final Book savedBook = target(BASE_URI).request(MediaType.APPLICATION_JSON_TYPE).post(bookEntity, Book.class);
Assert.assertNotNull(savedBook.getBookId());
}
}

运行测试方法,AirDynamicBindingFilter 的日志信息如预期输出,示例代码如下所示。

复制代码
Air-Dynamic-Binding-Filter initialized
Air-Dynamic-Binding-Filter invoked

书籍介绍

本书系统、深度讲解了如何基于 Java 标准规范实现 REST 风格的 Web 服务,由拥有 10 余年开发经验的阿里云大数据架构师撰写,第 1 版上市后广获赞誉,成为该领域的畅销书。第 2 版对全书进行了优化和重构,不仅根据 _ 新的技术版本对原有过时内容进行了更新,而且还根据整个技术领域的发展增添了新的内容。除此之外,还对第 1 版中存在的不足进行了优化,使得内容更加与时具进、更加有价值。不仅深刻解读了 _ 新的 JAX-RS 标准和其 API 设计,以及 Jersey 的使用要点和实现原理,还系统讲解了 REST 的基本理论,更重要的是从实践角度深度讲解了如何基于 Jersey 实现完整的、安全的、高性能的 REST 式的 Web 服务,书中包含大量示例代码,实战性强。

2016-08-20 01:299816

评论

发布
暂无评论
发现更多内容

Windows本地搭建RabbitMQ Server

北桥苏

Rabbit MQ RabbitMQ安装

2023 年最新 Java 面试必背八股文,1338 道最新大厂架构面试题

架构师之道

Java 面试

BATM面试Java岗:精选200+面试题及答案、6大重点规划和经验总结

Java你猿哥

Java MySQL JVM 多线程 java面试

国内商业BI工具介绍,瓴羊Quick BI、帆软怎么样

流量猫猫头

AntDB数据库携手金蝶Apusic应用服务器, 共促信创产业繁荣发展

亚信AntDB数据库

AntDB AntDB数据库 企业号 5 月 PK 榜

2023 开源之夏来啦!报名 MegEngine 项目,赢取万元奖金!

MegEngineBot

深度学习 开源之夏 MegEngine 学生比赛 奖金

Django查询、删除、更新数据

测吧(北京)科技有限公司

测试

Alibaba技术专家倾心五年打造!Java工程师成神之路(基础篇)

做梦都在改BUG

Java

MySQL主从配置+ThinkPHP5分布式数据库

北桥苏

MySQL 分布式 thinkphp

中国20强(上市)游戏公司2022年财报分析:营收结构优化,市场竞争进入白热化

易观分析

公司 游戏

大模型时代入场级技能:提示词工程!百度文心中文教程来啦

飞桨PaddlePaddle

百度飞桨

中建信息亮相华为中国合作伙伴大会2023

Geek_2d6073

从零开始打造一款基于SpringBoot+SpringCloud的后台权限管理系统

做梦都在改BUG

Java Spring Cloud Spring Boot 权限管理

【源码分析】【seata】at 模式分布式事务-server端与客户端交互

如果晴天

源码分析 分布式事务 分布式锁 seata Seata框架

重磅来袭!微服务的里程碑SpringCloudAlibaba

做梦都在改BUG

Java 架构 微服务 Spring Cloud spring cloud alibaba

以数据思维和技能提升数据应用测试实践 | 京东云技术团队

京东科技开发者

测试 测试覆盖率 数据思维 应用安全测试 企业号 5 月 PK 榜

Elasticsearch分布式搜索引擎的基本使用

北桥苏

php elasticsearch

开源轻量级 IM 框架 MobileIMSDK 的Uniapp客户端库已发布

JackJiang

网络编程 即时通讯 即时通讯IM

ZeroErr 零误框架

西风逍遥游

Fabarta 参加 2023 数云原力大会,与各方共同发布《2023 数据资产盘点实践白皮书》

Fabarta

数据要素 数据资产管理 数据资产化 数据要素流通

Django基本数据访问

测吧(北京)科技有限公司

测试

网页版超强ChatGPT插件应用ZipZap来了,每日免费10K Token足够使用

Ricky

openai ChatGPT GPT-4

数据库外网ip binlog主从配置

Java你猿哥

Java MySQL 后端 ssm

微信小程序二维码文件流上传到OSS解决方法

北桥苏

php OSS thinkphp

Java面试题大全(整理版)1000+面试题附答案详解最全面看完稳了

Java你猿哥

Java MySQL redis mybatis java面试

京东短网址高可用提升最佳实践 | 京东云技术团队

京东科技开发者

最佳实践 高可用设计 企业号 5 月 PK 榜 短网址

跑步课程导入能力,助力科学训练

HMS Core

HMS Core

面试官:说说 WebSocket 和 Socket 及 Http 的区别?

Java你猿哥

Java TCP ssm HTTP webSock

Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting、Self-Consistency等;项目实战搭建知识库内容机器人

汀丶人工智能

人工智能 深度学习 ChatGPT prompt learning

大厂工作四年Java经验总结了学习路线规划,所有私藏资料我都贡献出来了

Java你猿哥

Java Spring Boot JVM java基础 java面

夺冠秘诀?华为软件精英挑战赛两届冠军这样复盘比赛经验

华为云开发者联盟

云计算 华为云 华为云开发者联盟 企业号 5 月 PK 榜

Java RESTful Web Service实战_Java_韩陆_InfoQ精选文章