1. 简介
在上一篇 RESTEasy 教程 中,我们主要关注了 JAX-RS 2.0 的服务端实现。
而 JAX-RS 2.0 同时也引入了一个新的客户端 API,允许你调用远程的 RESTful Web 服务。Jersey、Apache CXF、Restlet 和 RESTEasy 都是目前比较主流的实现方案之一。
本文将带你了解如何通过 RESTEasy 提供的客户端 API 来消费 REST 接口。
2. 项目依赖配置
在你的 pom.xml
文件中添加以下依赖项:
<properties>
<resteasy.version>4.7.2.Final</resteasy.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-client</artifactId>
<version>${resteasy.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
...
</dependencies>
3. 客户端核心类
RESTEasy 客户端的核心由三个类构成:
- ✅
Client
- ✅
WebTarget
- ✅
Response
其中:
Client
是构建WebTarget
实例的工厂。WebTarget
表示一个具体的 URL 或 URL 模板,你可以基于它继续构建子资源路径或发起请求。
创建 Client
有两种方式:
- 使用标准的
org.jboss.resteasy.client.ClientRequest
- 使用 RESTEasy Proxy Framework(推荐):通过
ResteasyClientBuilder
构建
我们将重点介绍第二种方式 —— Proxy 模式。
Proxy 框架并不会像服务端那样处理请求,而是帮你构建 HTTP 请求并发送到远程服务。
所以我们可以直接写一个 Java 接口,并使用 JAX-RS 的注解来描述请求行为。
3.1. ServicesClient 接口定义
@Path("/movies")
public interface ServicesInterface {
@GET
@Path("/getinfo")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
Movie movieByImdbId(@QueryParam("imdbId") String imdbId);
@POST
@Path("/addmovie")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
Response addMovie(Movie movie);
@PUT
@Path("/updatemovie")
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
Response updateMovie(Movie movie);
@DELETE
@Path("/deletemovie")
Response deleteMovie(@QueryParam("imdbId") String imdbId);
}
3.2. Movie 数据模型
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "movie", propOrder = { "imdbId", "title" })
public class Movie {
protected String imdbId;
protected String title;
// getters and setters
}
3.3. 发起请求
下面是使用 Proxy 创建客户端并调用接口的完整示例:
String transformerImdbId = "tt0418279";
Movie transformerMovie = new Movie("tt0418279", "Transformer 2");
UriBuilder FULL_PATH = UriBuilder.fromPath("http://127.0.0.1:8082/resteasy/rest");
ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
ResteasyWebTarget target = client.target(FULL_PATH);
ServicesInterface proxy = target.proxy(ServicesInterface.class);
// POST
Response moviesResponse = proxy.addMovie(transformerMovie);
System.out.println("HTTP code: " + moviesResponse.getStatus());
moviesResponse.close();
// GET
Movie movies = proxy.movieByImdbId(transformerImdbId);
// PUT
transformerMovie.setTitle("Transformer 4");
moviesResponse = proxy.updateMovie(transformerMovie);
moviesResponse.close();
// DELETE
moviesResponse = proxy.deleteMovie(batmanMovie.getImdbId());
moviesResponse.close();
⚠️ 注意事项:
- RESTEasy 客户端底层使用的是 Apache HttpClient。
- 所有
Response
对象必须手动调用.close()
方法释放连接,否则后续请求会抛异常。 - DTO(如
Movie
)的序列化/反序列化工作由 JAXB 或 Jackson 自动完成,前提是类已正确标注。
3.4. 使用连接池优化并发性能
前面的例子中,默认只允许一个连接。如果你连续发起多个请求而不关闭响应,就会报错:
java.lang.IllegalStateException:
Invalid use of BasicClientConnManager: connection still allocated.
Make sure to release the connection before allocating another one.
这是因为默认使用的 HttpClient 是 SingleClientConnManager
,只能维持单个连接。
为了解决这个问题,我们需要启用连接池:
PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build();
cm.setMaxTotal(200); // 最大连接数
cm.setDefaultMaxPerRoute(20); // 每个路由最大连接数
ApacheHttpClient43Engine engine = new ApacheHttpClient43Engine(httpClient);
ResteasyClient client = ((ResteasyClientBuilder) ClientBuilder.newBuilder()).httpEngine(engine).build();
ResteasyWebTarget target = client.target(FULL_PATH);
ServicesInterface proxy = target.proxy(ServicesInterface.class);
✅ 这样就可以支持多个并发请求,避免频繁释放连接带来的麻烦。
4. 总结
这篇文章带你快速入门了 RESTEasy Proxy Framework,并通过简单的代码展示了如何封装一个 REST 客户端。
Proxy 框架本质上是 JAX-RS 服务端规范的一个“镜像”版本,它把服务端的声明式接口变成了客户端的调用入口。
文中示例项目可以在 GitHub 上找到:sample project on GitHub。