vlambda博客
学习文章列表

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Chapter 9. Using JAX-RS 2.0 in Java EE 7 with RESTEasy

JSR 311 (http://jcp.org/en/jsr/detail ?id=311) 指定 Java API for RESTful Web services (JAX-RS) 用于开发< strong>REST具象状态传输)使用 Java 的 Web 服务。 REST 是一种独立于协议、松散耦合的分布式系统软件架构风格。 RESTful Web 服务公开一组资源,这些资源只是信息的来源,由 URI 统一资源标识符)。 RESTful Web 服务遵循以下 RESTful 原则:

  • 每个资源都有一个唯一的基础 URI

  • 用于调用 Web 服务操作的 HTTP 协议方法,如 GETPUTPOST< /code> 和 DELETE 被使用

  • 客户端向服务发送请求,服务将请求的资源的表示返回给客户端

  • 客户端会话不存储在服务器上,这使得在集群环境中以更少的数据复制更容易扩展服务

JSR 339 (https://www.jcp.org/en /jsr/detail?id=339) 开发 JAX-RS 2.0 版本。 JAX-RS 2.0 提供了几个新功能,例如客户端 API,支持 验证 过滤器拦截器异步处理。我们将使用 RESTEasy (http://resteasy.jboss.org/) 实现。本章包含以下部分:

  • 设置环境

  • 客户端 API

  • 过滤器和拦截器

  • 异步处理

  • 取消请求

  • 会话 bean EJB 资源

  • 从客户端进行异步调用

Setting up the environment


我们需要 安装以下软件:

设置环境变量JAVA_HOMEJBOSS_HOMEMAVEN_HOME。添加%JAVA_HOME%/bin%MAVEN_HOME%/bin%JBOSS_HOME%/ binPATH 环境变量。

创建一个 WildFly 8.1.0 运行时,如 第 1 章 EJB 3.x 入门。如 java:jboss/datasources/MySQLDS 创建 MySQL 数据源/book/application-development/9781783288908/1" linkend="ch01">第 1 章EJB 3.x 入门 .

Creating a Java EE web project


首先,我们需要创建一个Java EE web项目,您需要为其选择文件 | | 其他。在 New 中,选择 Web | Java EE Web 项目,如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Java EE Web 项目 向导中,选择 创建一个空白项目并选择WildFly 8.x Runtime作为Target Runtime,如下图所示。运行测试以查找是否安装了所需的插件。然后,点击下一步

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

指定 项目名称 (jboss- resteasy) 和 Package (org.jboss.resteasy),然后点击下一步如下:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

指定 组 ID (org. jboss.resteasy), Artifact Id (jboss-resteasy), 版本1.0.0)和包< /strong> (org.jboss.resteasy),然后点击下一步 ,如此处所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

jboss-resteasy Maven 项目被创建并被添加到 < strong>Project Explorer,如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

接下来,添加一个 JAX-RS 资源类(HelloWorldResource),它只是一个Java 类。选择文件 | | Other,并在 New 中选择 Java | 并点击下一步。选择 Source folder (jboss-resteasy/src/main/java) 并指定 Package (org.jboss.resteasy.rest) 和类 < strong>名称 (HelloWorldResource),然后点击Finish ,如此处所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

同样,添加一个 Java 客户端类(RESTEasyClient),如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

jboss-resteasy 应用程序的目录 结构如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

JAX-RS-和RESTEasy相关的依赖添加到pom.xml中,如下:

<dependencies>

  <!-- Import the JAX-RS API, we use provided scope as the API is included in JBoss WildFly -->
  <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>jaxrs-api</artifactId>
    <version>3.0.10.Final</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jackson-provider</artifactId>
    <version>3.0.10.Final</version>
    <scope>provided</scope>
  </dependency>
  <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jaxrs</artifactId>
    <version>3.0.10.Final</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.3.6</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpcore</artifactId>
    <version>4.3.3</version>
    <scope>provided</scope>
  </dependency>

  <dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-client</artifactId>
    <version>3.0.10.Final</version>
    <scope>provided</scope>
  </dependency>

在构建中,添加 Maven 编译器插件和 Maven WAR 插件。在 Maven WAR 插件配置中,指定构建应用程序要部署到的输出目录:C:\wildfly-8.1.0.Final\standalone\deployments 目录:

<build>
<!-- Maven will append the version to the finalName (which is the name given to the generated war, and hence the context root) -->
  <finalName>${project.artifactId}</finalName>
  <plugins>
    <plugin>
      <artifactId>maven-war-plugin</artifactId>
      <version>${version.war.plugin}</version>
      <configuration>
        <outputDirectory>C:\wildfly-8.1.0.Final\standalone\deployments</outputDirectory>
        <failOnMissingWebXml>false</failOnMissingWebXml>
      </configuration>
    </plugin>
  </plugins>
</build>

完整的 pom.xml 可在本章的代码下载中找到。接下来,创建 Web 部署描述符 web.xml。选择文件 | | 其他。在 New 中,选择 JBoss Tools Web | Web Descriptor并点击Next,如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

New Web Descriptor File 中,选择 文件夹WEB-INF目录,指定名称< /span> 作为 web.xml,并选择 Version 作为 3.1,如下图:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

web.xml 部署 描述符被添加到 WEB-INF< /code>,如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

将 RESTEasy 调度程序 servlet 添加到 web.xml 文件,包括其 URL 映射。添加 RESTEasy 扫描 JAX-RS 类所需的上下文参数。此外,为映射前缀的 RESTEasy servlet 添加上下文参数。 web.xml 文件列举如下:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.1" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
  <context-param>
    <param-name>resteasy.servlet.mapping.prefix</param-name>
    <param-value>/rest</param-value>
  </context-param>
  <context-param>
    <param-name>resteasy.scan</param-name>
    <param-value>true</param-value>
  </context-param>
  <listener>
    <listener-class>org.jboss.resteasy.plugins.server.servlet.ResteasyBootstrap</listener-class>
  </listener>
  <servlet>
    <servlet-name>Resteasy</servlet-name>
    <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServletDispatcher</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>Resteasy</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
</web-app>

The Client API


Client API 是用于访问网络资源和集成的高级API与 JAX-RS 提供程序一起使用,并且包含在 javax.ws.rs.client 包中。

Note

以前,不同的实现提供了客户端 API,但在 JAX-RS 2.0 中,客户端 API 作为核心 API 提供。

Creating a client instance

Client 实例 需要构建和运行客户端请求以访问或使用 Web 资源。在 RESTEasy 客户端类 RESTEasyClient.java 中,从 ClientBuilderClient 实例> 使用 newClient() 方法如下:

Client client = ClientBuilder.newClient();

可以使用 register() 方法通过 Client 对象配置提供程序、过滤器和功能。例如,org.jboss.resteasy.plugins.providers.JaxrsFormProvider.class 提供者类注册为 如下:

client.register(org.jboss.resteasy.plugins.providers.JaxrsFormProvider.class);

Accessing a resource

Client API 用于访问网络资源,如下所示:

  1. 使用 Client 的重载 target() 方法从资源 URI 创建一个 WebTarget 对象 对象。附加到 URI 的路径是为了使 REST 服务能够处理多个输入:

    WebTarget target = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld");
  2. 如果需要,使用 path() 方法将一个或多个路径元素添加到 WebTarget 对象,该方法返回 WebTarget 对象:

    WebTarget target = target.path("text");
  3. 使用重载的 request() 方法从 WebTarget 对象创建请求,您需要在其中定义接受的响应媒体类型.使用重载的 get() 方法为请求调用 HTTP GET 方法,以获取作为 响应对象:

    Response response=target.request("text/plain").get();
  4. String 对象的形式获取消息实体输入流:

    String value = response.readEntity(String.class);
  5. fluent API 可用于构建和提交客户端请求,并通过链接方法调用获取响应:

    String response = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld").path("text").request("text/plain").get(String.class);

RESTEasyClient.java 类列举如下:

package org.jboss.resteasy.rest;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;

import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.*;

import org.jboss.resteasy.client.jaxrs.ResteasyClient;

public class RESTEasyClient {
  
  public static void main(String[] args) {
    
    Client client =	ClientBuilder.newClient();
    
    String response = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld").path("text").request("text/plain").get(String.class);
    
    
    System.out.println(response);
  }
  
}

/helloworld URI 路径中创建一个 资源类来测试客户端 API。在相对 URI 路径 /text 处添加资源方法以从名称返回 Hello 消息。资源类HelloWorldResource列举如下:

package org.jboss.resteasy.rest;

import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;


@Path("/helloworld")
public class HelloWorldResource {
  
  @GET
  @Produces("text/plain")
  @Path("/text")
  public String getClichedMessage() {
    
    return "Hello John Smith";
  }
  
}

要测试资源类和客户端,编译、打包并部署 jboss-resteasy 应用程序到 WildFly。我们将作为 WildFly deployments 目录的输出目录添加到 Maven WAR 插件的配置中,如下所示:

<plugin>
  <artifactId>maven-war-plugin</artifactId>
  <version>${version.war.plugin}</version>
  <configuration>
    <outputDirectory>C:\wildfly-8.1.0.Final\standalone\deployments</outputDirectory>
    <failOnMissingWebXml>false</failOnMissingWebXml>
  </configuration>
</plugin>

右键单击 pom.xml 上的 并选择 运行作为 | Maven install,如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

jboss-resteasy 应用程序被编译、构建并输出到 WildFly 8.1 deployments 目录由 Console 中的 "strong">BUILD SUCCESS 消息,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

现在,登录到 WildFly 8.1 管理控制台并点击< span class="strong">管理部署。 jboss-resteasy.war 应用程序应列为已部署,如以下屏幕截图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

要测试客户端,请右键单击 Project Explorer 中的 RESTEasyClient.java 类并 < a id="id522" class="indexterm">选择 运行方式 | Java 应用程序,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

客户端应用程序运行以调用资源类并生成输出,如以下 Console 所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Setting a query parameter

要调用带参数的 资源方法,可以使用 @QueryParam 注释将请求参数绑定到资源方法参数。在上一小节中使用的资源类的变体中,将 String 参数添加到资源方法。使用 @QueryParam 注释参数声明并将其默认值设置为 DefaultValue

@GET
@Produces("text/plain")
@Path("/text")
public String getClichedMessage(@QueryParam("name") @DefaultValue("John Smith") String name) {
  return "Hello " +name;
}

在客户端类中,可以使用 queryParam() 方法在请求中发送查询参数,如下所示:

String response = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld").path("text").queryParam("name", "John Smith").request("text/plain").get(String.class);

或者,可以在请求 URI 中包含查询参数,如下所示:

String response = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld/text?name=John Smith").request("text/plain").get(String.class);

Setting a template parameter

资源 URI 也可以使用模板参数构建。在 HelloWorldResource 的变体中,在 @Path< 中指定模板参数 {name} /code> 注释资源方法。使用 @PathParam 注解将模板参数绑定到资源方法参数:

@GET
@Produces("text/plain")
@Path("/text/{name}")
public String getClichedMessage(@PathParam("name") String name) {
  return "Hello " +name;
}

RESTEasyClient 类中,在资源 URI 中包含 {name} 模板参数的值,如下所示:

String response = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld/text/John Smith").request("text/plain").get(String.class);

重新部署 jboss-resteasy 应用程序并重新运行客户端以产生相同的输出。

Filters and interceptors


过滤器 提供 扩展功能,例如日志记录和身份验证。拦截器提供实体压缩等扩展功能。在本节中,我们将讨论 JAX-RS 2.0 实现中特定扩展点对过滤器的支持。 JAX-RS 2.0 中提供了两种类型的过滤器:客户端过滤器 容器过滤器 客户端过滤器位于客户端,容器过滤器位于容器端。客户端过滤器对应的接口包含在客户端 API 中,分别是 javax.ws.rs.client.ClientRequestFilterjavax.ws.rs。客户端.ClientResponseFilter 容器过滤器的接口,包含在服务器 API 中,是 code class="literal">javax.ws.rs.container.ContainerRequestFilter 和 javax.ws.rs.container.ContainerResponseFilter。要被 JAX-RS 运行时发现,实现接口的过滤器必须使用 @Provider 注释进行注释。创建Java类LoggingFilter(用于容器过滤器示例)和ClientFilter(用于客户端过滤器示例),如< span class="strong">项目浏览器。

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

在讨论客户端和容器过滤器之前,我们需要讨论过滤器拦截客户端和服务器之间通信的连接点:

  1. ClientRequestFilter 在客户端 HTTP 请求发送到服务器之前拦截 通信。

  2. ContainerRequestFilter 拦截 在客户端发送到服务器之后但在 JAX-RS 资源方法之前调用。

  3. ContainerResponseFilter 在调用 JAX-RS 资源方法之后但在响应发送回客户。

  4. ClientResponseFilter 在服务器 HTTP 响应发送到客户端之后但在响应解组之前调用.

请求/响应拦截的连接点如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Creating a client filter

首先,我们将通过一个示例来讨论客户端过滤器。 ClientRequestFilter 在 HTTP 请求被传递到网络之前在调用管道中运行。 ClientRequestFilter 应由 @Provider 注释,这是 JAX-RS 运行时在扫描阶段发现的标记. ClientResponseFilter 在从服务器接收到响应之后并且在控件返回给应用程序之前运行。使 ClientFilter 类实现 ClientRequestFilterClientResponseFilter 接口。添加 filter(ClientRequestContext arg0)filter(ClientRequestContext arg0, ClientResponseContext arg1) 方法的实现。在ClientRequestFilter 实现方法filter(ClientRequestContext arg0)中,使用getHeaderString( ClientRequestContext 的 String) 方法。例如,Accept-CharsetAccept-Encoding 标头给出以下输出:

System.out.println("Accept-Charset: " + arg0.getHeaderString("Accept-Charset"));
System.out.println("Accept-Encoding: " + arg0.getHeaderString("Accept-Encoding"));

使用 setUri(URI) 方法设置新的资源 URI,如下所示:

arg0.setUri(new URI("http://localhost:8080/jboss-resteasy/rest/helloworld/text/Smith,John"));

ClientFilter 是列举如下:

package org.jboss.resteasy.rest;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

import javax.ws.rs.client.ClientRequestContext;
import javax.ws.rs.client.ClientRequestFilter;
import javax.ws.rs.client.ClientResponseContext;
import javax.ws.rs.client.ClientResponseFilter;

import javax.ws.rs.core.Response;
import javax.ws.rs.ext.Provider;

@Provider
public class ClientFilter implements ClientRequestFilter, ClientResponseFilter {
  
  @Override
  public void filter(ClientRequestContext arg0, ClientResponseContext arg1)
  throws IOException {
  }
  @Override
  public void filter(ClientRequestContext arg0) throws IOException {
    
    System.out.println("Entity Class: " + arg0.getEntityClass());
    System.out.println("Accept: " + arg0.getHeaderString("Accept"));
    System.out.println("Accept-Charset: "
    + arg0.getHeaderString("Accept-Charset"));
    System.out.println("Accept-Encoding: "
    + arg0.getHeaderString("Accept-Encoding"));
    System.out.println("Accept-Language: "
    + arg0.getHeaderString("Accept-Language"));
    System.out.println("Accept-Ranges: "
    + arg0.getHeaderString("Accept-Ranges"));
    System.out.println("Allow: " + arg0.getHeaderString("Allow"));
    System.out.println("Authorization: "
    + arg0.getHeaderString("Authorization"));
    System.out.println("Cache-Control: "
    + arg0.getHeaderString("Cache-Control"));
    System.out.println("Content-Encoding: "
    + arg0.getHeaderString("Content-Encoding"));
    System.out.println("Content-Location: "
    + arg0.getHeaderString("Content-Location"));
    System.out.println("Accept-Encoding: "
    + arg0.getHeaderString("Accept-Encoding"));
    System.out.println("Content-Type: "
    + arg0.getHeaderString("Content-Type"));
    System.out.println("Host: " + arg0.getHeaderString("Host"));
    System.out.println("Pragma: " + arg0.getHeaderString("Pragma"));
    System.out.println("Server: " + arg0.getHeaderString("Server"));
    System.out.println("User-Agent: " + arg0.getHeaderString("User-Agent"));
    
    try {
      arg0.setUri(new URI(
      "http://localhost:8080/jboss-resteasy/rest/helloworld/text/Smith,John"));
    } catch (URISyntaxException e) {
      // TODO Auto-generated catch block
      e.printStackTrace();
    }
    //arg0.abortWith(Response.notAcceptable(null).build());
  }
}

对于客户端过滤器可能发生的问题,请参阅解决常见问题部分/span> 在本章末尾。在客户端类中,RESTEasyClient 向客户端注册客户端过滤器:

client.register(ClientFilter.class);

我们将使用以下根资源类 HelloWorldResource 来测试客户端过滤器:

package org.jboss.resteasy.rest;
import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;

@Path("/helloworld")
public class HelloWorldResource {

  @GET
  @Produces("text/plain")
  @Path("/text/{name}")
  public String getClichedMessage(@PathParam("name") String name) {
    return "Hello " +name;
  }
}

用于测试客户端过滤器的客户端类 RESTEasyClient 列出如下:

package org.jboss.resteasy.rest;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.*;

public class RESTEasyClient {

  public static void main(String[] args) {
    Client client = ClientBuilder.newClient();
    client.register(ClientFilter.class);
    String response = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld/text/John Smith").request("text/plain").get(String.class);
    System.out.println("Text response "+ response);
  }
}

重新部署 jboss-resteasy 应用程序。要重新部署,请右键单击 Project Explorer 中的 pom.xml 并选择 运行方式 | Maven clean,然后右键单击 pom.xml 并选择 运行方式 | Maven 安装。运行 RESTEasyClient.java 类,在 Console 屏幕中生成如下输出,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

由于我们修改了客户端过滤器中的资源URI,响应消息不是针对客户端类中指定的John Smith,而是针对史密斯,约翰

过滤器链处理可能会中止,并使用 abortWith(Response response) 方法将响应返回给客户端。在客户端获得响应之前应用客户端响应过滤器。例如,断开过滤器链并返回 notAcceptable(null) 响应:

arg0.abortWith(Response.notAcceptable(null).build());

保持 RESTEasyClientHelloWorldResource 相同,并重新部署 jboss-restaesy 应用程序。重新运行 RESTEasyClient 类以生成以下输出,其中包括如下所示的 NotAcceptableException

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Creating a container filter


容器过滤器服务器API过滤器。 A ContainerRequestFilter 过滤器在收到来自客户端的请求后运行。 ContainerResponseFilter 在将 HTTP 响应传递给客户端之前在响应管道中运行。接下来,我们将创建一个容器过滤器,用于记录/输出有关请求的一些信息。 ContainerRequestFilter 接口中提供了匹配前后的扩展点。匹配前过滤器在请求与资源方法匹配之前运行,匹配后过滤器在资源方法匹配后应用;默认是赛后。我们将预匹配与 @PreMatching 注释一起使用。使用 @Provider 注释过滤器类,以便 JAX-RS 运行时在扫描阶段发现过滤器。

制作示例容器过滤器LoggingFilter,实现ContainerRequestFilterContainerResponseFilter接口,并为 filter(ContainerRequestContext requestContext)filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) 方法提供实现。在ContainerRequestFilter实现方法filter(ContainerRequestContext requestContext)中,用getMethod输出请求方法() 方法,请求 URI 使用 getUriInfo().getAbsolutePath(),媒体类型使用 getMediaType() ,以及使用 getAcceptableMediaTypes() 的可接受媒体类型。在 LoggingFilter 中包含一个无参数构造函数,以便可以实例化过滤器。使用 register 方法将 LoggingFilter 注册到客户端配置中。注释掉 ClientFilter 的注册,因为我们将只应用 RESTEasyClient< 中的 LoggingFilter /代码>类:

Client client = ClientBuilder.newClient();
//client.register(ClientFilter.class);
client.register(LoggingFilter.class);

默认情况下,容器过滤器 绑定到客户端请求发送到的所有资源,但可以使用 @NameBinding 注释:

LoggingFilter.java 列举如下:

package org.jboss.resteasy.rest;

import java.io.IOException;
import java.util.Iterator;
import java.util.List;
import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.container.ContainerRequestFilter;
import javax.ws.rs.container.ContainerResponseContext;
import javax.ws.rs.container.ContainerResponseFilter;
import javax.ws.rs.container.PreMatching;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.Provider;

@Provider
@PreMatching
public class LoggingFilter implements ContainerRequestFilter,
ContainerResponseFilter {
  
  public LoggingFilter() {
  }
  
  @Override
  public void filter(ContainerRequestContext requestContext)
  throws IOException {
    System.out.println("Request Method: " + requestContext.getMethod());
    System.out.println("Request URI: "+ requestContext.getUriInfo().getAbsolutePath());
    System.out.println("Media Type : "+ requestContext.getMediaType());
    List<MediaType> mediaTypes = requestContext.getAcceptableMediaTypes();
    Iterator<MediaType> iter = mediaTypes.iterator();
    System.out.println("Acceptable Media Types: ");
    while (iter.hasNext()) {
      MediaType mediaType = iter.next();
      System.out.println(mediaType.getType() + ", ");
      
    }
  }
  @Override
  public void filter(ContainerRequestContext requestContext,
  ContainerResponseContext responseContext) throws IOException {
    
  }
}

保持 RESTEasyClientHelloWorldResource 与 < code class="literal">ClientFilter 示例并重新部署 jboss-restaesy 应用程序。运行 RESTEasyClient 应用程序以生成容器过滤器显示的输出,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Asynchronous processing


JAX-RS 2.0 在客户端 API 和服务器 API。默认情况下,当客户端向服务器发送请求时,它会暂停所有其他处理,直到收到响应。通过异步处理,客户端暂停与服务器的连接并在生成服务器响应并将其发送回客户端时继续处理。当响应传递给客户端时,客户端重新建立与服务器的连接并接受响应。下图说明了同步和异步请求/响应中的客户端-服务器模型:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

同样,默认情况下,服务器线程会在等待外部进程完成一个客户端请求时阻止所有其他传入的客户端请求。通过异步处理,服务器暂停与客户端的连接,以便它可以接受其他客户端请求。当客户端请求的响应可用时,服务器重新建立与客户端的连接并发送请求。在本节中,我们将通过一个示例来讨论异步处理。创建 Java 类 AsyncResource(用于根资源类)、AsyncClient(用于客户端)和 AsyncTimeoutHandler(用于超时处理程序)。 async 类的目录结构在 Project Explorer 中显示如下:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

服务器 API 添加了 javax. ws.rs.container.AsyncResponse 接口表示异步响应,用于服务器端处理异步响应。 javax.ws.rs.container.Suspended 接口用于将暂停的 AsyncResponse 实例注入资源方法参数。 AsyncResponse 实例绑定到活动的客户端请求,可用于在响应可用时异步提供响应。当要向客户端发送响应时,AsyncResponse 实例会恢复暂停的请求。

Suspended response

使用 @ 注入暂停的 AsyncResponse 的资源或 子资源方法Suspended 注释必须将返回类型声明为 void。如果注入的 AsyncResponse 实例没有取消或恢复暂停的异步响应,则响应将无限期暂停。在 AsyncResource 根资源类中,添加一个资源方法(例如称为 timeout),其中注入了一个暂停的 AsyncResponse 实例使用 @Suspended 注释的资源方法参数,如下面的清单所示。模板参数 {timeout} 包含在资源方法的路径 URI 中:

package org.jboss.resteasy.rest.async;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
 

@Path("/helloworld")
public class AsyncResource {

  @GET
  @Path("/timeout/{timeout}")
  Produces("text/plain")
  public void timeout(@PathParam("timeout") String timeoutStr,@Suspended AsyncResponse ar) {}
}

AsyncClient 中,class 包含值 60 用于请求 URI 中的 {timeout} 模板参数,如以下清单所示:

package org.jboss.resteasy.rest.async;

import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.*;

public class AsyncClient {
  
  public static void main(String[] args) {
    
    Client client =	ClientBuilder.newClient();
    
    WebTarget target = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld/timeout/60");
    
    String response = target.request("text/plain").get(String.class);
    System.out.println("Text response: " + response);
    
  }
  
}

运行 pom.xml 文件以 部署 jboss-resteasy< /代码>应用程序。当 AsyncClient 应用程序运行时,服务器不返回响应,因为异步响应被挂起并返回以下异常:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Resuming request processing

暂停 AsyncResponse 可以选择恢复请求处理,通常在响应可用时,使用 < code class="literal">resume(Object) 方法。使用ResponseBuilder对象构建响应,可以通过Response static方法获取ok (对象)。使用 ResponseBuilder 方法 type(MediaType) 设置响应的媒体类型并创建一个 使用 build() 方法的响应 对象。使用 resume(Object) 方法恢复暂停的请求处理以发送 Response 对象。 AsyncResource 根资源类如下:

package org.jboss.resteasy.rest.async;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/helloworld")
public class AsyncResource {
  
  @GET
  @Path("/timeout/{timeout}")
  @Produces("text/plain")
  public void timeout(@PathParam("timeout") String timeoutStr,
  @Suspended AsyncResponse ar) {
    
    try {
      Response hello = Response.ok("Hello John Smith").type(MediaType.TEXT_PLAIN).build();
      ar.resume(hello);
      
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}

客户端类 Suspended response 部分中列出的相同这一章。要编译和打包 jboss-resteasy 应用程序,右键单击 pom.xml 并选择 运行方式 | Maven 安装。启动 WildFly 8.1 服务器以部署应用程序,应用程序部署完成后,运行客户端类 AsyncClient。右键单击 Package Explorer 中的 AsyncClient.java 并选择 运行方式 | Java 应用程序。客户端运行产生输出,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

发送的 Response 对象可能是 String 字面意思。如果在 resume(Object) 中使用了 String 文字,如此处所示,则 < strong>Hello after timeout 消息生成:

ar.resume("Hello after a timeout");

Resuming a request with a suspend timeout handler

AsyncResponse 实例可以选择更新挂起集数据以设置新的挂起超时。使用 setTimeout(long time, TimeUnit unit) 方法设置新的暂停超时,如下所示:

ar.setTimeout(timeout, TimeUnit.SECONDS);

ar 变量是 AsyncResponse 对象。新的暂停超时值将覆盖先前的超时值。在第一次调用 setTimeout 时,暂停超时已从无限期暂停变为暂停指定的超时值。 javax.ws.rs.container.TimeoutHandler 接口用于提供超时事件的自定义解决方案。超时事件的默认解决方案是让 JAX-RS 2.0 运行时生成 Service不可用 异常。使用 setTimeoutHandler(TimeoutHandler handler) 方法设置暂停超时处理程序:

ar.setTimeoutHandler(new AsyncTimeoutHandler("Timeouted after " + timeout + " seconds"));

用于设置挂起超时处理程序的 AsyncResource 类列出如下:

package org.jboss.resteasy.rest.async;

import java.util.concurrent.TimeUnit;

import javax.ws.rs.GET;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.Path;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Path("/helloworld")
public class AsyncResource {
  
  @GET
  @Path("/timeout/{timeout}")
  @Produces("text/plain")
  public void timeout(@PathParam("timeout") String timeoutStr,
  @Suspended AsyncResponse ar) {
    
    try {
      long timeout = Long.parseLong(timeoutStr);
      System.out.println("timeout - enter with timeout=" + timeoutStr
      + "s");
      ar.setTimeoutHandler(new AsyncTimeoutHandler("Timeouted after "
      + timeout + " seconds"));
      ar.setTimeout(timeout, TimeUnit.SECONDS);
    } catch (Exception e) {
      System.out.println(e.getMessage());
    }
  }
}

使 AsyncTimeoutHandler 超时处理程序类实现TimeoutHandler界面。在 AsyncTimeoutHandler 中,实现 handleTimeout(AsyncResponse asyncResponse) 方法,可以使用以下方法之一处理暂停超时:

  • 可以使用 resume(Object) 方法恢复异步响应

  • 可以使用 resume(Throwable) 方法抛出异常来恢复响应

  • 可以使用 cancel() 方法取消响应

  • 可以使用 setTimeout(long time, TimeUnit unit) 方法的另一个调用来延长挂起超时

AsyncTimeoutHandler 类中,使用 resume( Object) 方法向客户端返回响应。 AsyncTimeoutHandler 类列举如下:

package org.jboss.resteasy.rest.async;
import java.util.concurrent.TimeUnit;
import javax.ws.rs.container.AsyncResponse;
import javax.ws.rs.container.TimeoutHandler;
public class AsyncTimeoutHandler implements TimeoutHandler {
  private String _message;
  boolean keepSuspended = false;
  //boolean cancel = true;
  boolean cancel = false;
  int retryPeriod = 10;
  AsyncTimeoutHandler(String message) {
    _message = message;
  }
  @Override
  public void handleTimeout(AsyncResponse ar) {
    System.out.println("handleTimeout - enter");
    if (keepSuspended) {
      ar.setTimeout(10, TimeUnit.SECONDS);
    } else if (cancel) {
      ar.cancel(retryPeriod);
    } else {
      ar.resume(_message);
    }
    /*Response hello = Response.ok("Hello after a timeout").type(MediaType.TEXT_PLAIN).build();
    ar.resume(hello);*/
  }
}

使用 Maven 重新部署 应用程序并重新运行 AsyncClient 类。应用新的暂停超时,响应暂停 60 秒,如消息 timeout-enter with timeout=60s 所示,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

当请求处理恢复时,如下图所示的响应被发送到客户端并从客户端类AsyncClient.java输出:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

之前的示例中,我们在超时处理程序中恢复了请求。可以在资源方法中恢复请求,其中在新的挂起超时运行之前设置了新的挂起超时和超时处理程序,如下面清单中的资源方法所示:

@GET
@Path("/timeout/{timeout}")
@Produces("text/plain")
public void timeout(@PathParam("timeout") String timeoutStr, @Suspended AsyncResponse ar) {
  try {
    long timeout = Long.parseLong(timeoutStr);
    System.out.println("timeout - enter with timeout=" + timeoutStr + "s");
    ar.setTimeoutHandler(new AsyncTimeoutHandler("Timeouted after " + timeout + " seconds"));
    ar.setTimeout(timeout, TimeUnit.SECONDS);
    Response hello = Response.ok("Hello before the suspend timeout of 60 seconds has run").type(MediaType.TEXT_PLAIN).build();
    ar.resume(hello);
  } catch (Exception e) {
    System.out.println(e.getMessage());
  }
}

要应用新的挂起超时,必须在超时处理程序中恢复请求。如果设置了新的挂起超时和超时处理程序,并且挂起的超时处理程序不执行任何操作,则默认解决方案是使用 ServiceUnAvailableException 异常恢复请求处理。要恢复发送响应的请求,必须使用 resume(Object) 方法显式恢复请求。

Cancelling a request

AsyncResponse 实例可以在暂停的超时处理程序或使用重载的资源方法中取消响应 cancel() 方法:

boolean cancel = true;
int retryPeriod = 10;
if (cancel) {
  System.out.println("Cancel the suspeneded request processing");
  ar.cancel(retryPeriod);
}

客户端在取消响应时得到如下异常,如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Session bean EJB resource


JAX-RS 2.0 支持将 无状态和单例 会话 bean 作为根资源类。在本节中,我们将 AsyncResource 根资源类作为无状态会话 bean 运行。我们向 pom.xml 添加了一个与 EJB 相关的依赖项。

使用 Stateless 注释对 AsyncResource 进行注释。 @Path 注释也必须应用于类:

@Path("/helloworld")
@Stateless
public class AsyncResource {}

当应用程序运行时,根资源类被添加到 JNDI 中,就像任何其他会话 bean 一样。 AsycnResource 的 JNDI 绑定如下所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Making an asynchronous call from the client


我们有 as 但只讨论了服务器 API 中的异步支持。客户端 API 中也提供了异步请求处理。可以使用 async() 方法异步发送客户端请求。

异步调用资源意味着调用立即返回。或者,可以使用 InvocationCallback 接口注册回调。实现 completed(RESPONSE response)failed(Throwable throwable) 方法。 completed(RESPONSE response) 方法在调用成功完成时调用,failed(Throwable throwable) 方法在调用成功时调用调用失败。在 AsyncClient.java 客户端中异步发出客户端请求,如下所示:

WebTarget target = client.target("http://localhost:8080/jboss-resteasy/rest/helloworld/timeout/60");
AsyncInvoker asyncInvoker = target.request("text/plain").async();
asyncInvoker.get(new InvocationCallback<String>() {
  @Override
  public void completed(String response) {
    System.out.println("Invocation completed and response available");
  }
  @Override
  public void failed(Throwable arg0) {}
});
System.out.println("Call to get returns immediately");

在资源方法中设置暂停的超时和超时处理程序,并在超时处理程序中恢复请求,如下所示:

Response hello = Response.ok("Hello after a timeout").type(MediaType.TEXT_PLAIN).build();
ar.resume(hello);

应用程序运行时,调用立即返回并显示消息 Call to get returns immediate(消息可以获取输出并继续处理而不会注意到消息)。

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

暂停超时以消息开始。这显示在以下屏幕截图中:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

暂停超时运行后,将返回响应,如以下屏幕截图所示。调用立即返回时的消息输出也显示在此处:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Fixing a common issue


RESTEasy 应用程序可能会产生以下错误:

The Provider must implement at least one of the following interfaces: javax.ws.rs.ext.MessageBodyReader, javax.ws.rs.ext.MessageBodyWriter, javax.ws.rs.ext.ExceptionMapper or javax.ws.rs.ext.ContextResolver."

要修复错误,请选择 Windows | 首选项。在 Preferences, 中选择 JBoss Tools | JAX-RS | JAX-RS 验证器。选择 JAX-RS Providers 并将 Missing Provider implementation 设置为 < span class="strong">忽略,然后点击应用OK,如下图所示:

读书笔记《advanced-java-ee-development-with-wildfly》在带有RESTEasy的Java EE 7中使用JAX-RS 2.0

Summary


在本章中,我们通过一个使用 RESTEasy 实现的示例讨论了 JAX RS 2.0 中的显着新特性。我们讨论了新的客户端 API、过滤器和拦截器、异步处理、取消请求以及使用 EJB 作为 REST Web 服务资源。

在下一章中,我们将讨论 Java EE 7 中引入的另一个新特性,即对 JSON 处理的支持。