如此强大的REST Client API为什么都不用?

开发 前端
JAX-RS(Java API for RESTful Web Services)是一个用于构建和管理 RESTful Web 服务的标准规范。JAX-RS 提供了一系列强大的功能,使得开发者可以轻松地创建、消费和管理 RESTful 服务。

1. 简介

本篇文章我们将介绍JAX-RS,使用Client API实现REST API接口的调用。

JAX-RS(Java API for RESTful Web Services)是一个用于构建和管理 RESTful Web 服务的标准规范。JAX-RS 提供了一系列强大的功能,使得开发者可以轻松地创建、消费和管理 RESTful 服务。

我们可以通过JAX-RS API编写REST接口(Spring对应的Controller接口),也可以使用其提供的Client API实现 REST接口的调用。如下,我们实现REST API的定义:

import javax.ws.rs.*;
import javax.ws.rs.core.MediaType;
@Path("/users")
public class UserResource {
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public List<User> getUsers() {}
  @POST
  @Consumes(MediaType.APPLICATION_JSON)
  public void createUser(User user) {}
  @PUT
  @Path("/{id}")
  @Consumes(MediaType.APPLICATION_JSON)
  public void updateUser(@PathParam("id") int id, User user) {}
  @DELETE
  @Path("/{id}")
  public void deleteUser(@PathParam("id") int id) {}
}

与Spring MVC的Controller接口相比,除了注解不一样也没有什么区别了,但是这里的JAX-RS是 Jakarta EE 的一部分是标准。

本篇文章的重点是通过JAX-RS的Client API实现远程接口的调用,所以上面对服务接口的实现有兴趣的可以查看《jaxrs-2_1-final-spec.pdf》规范文档。

既然有了这么多的方式调用,为什么还要使用JAX-RS呢?我们这里就与Spring的RestTemplate,WebClient进行对比。

  • 标准化和兼容性JAX-RS:作为 Jakarta EE 的标准规范,JAX-RS 具有广泛的兼容性和标准化支持。这意味着无论你使用哪种 JAX-RS 实现(如 Jersey、RESTEasy),都具有一致的行为。RestTemplate:Spring 框架的一部分,主要用于 Spring 生态系统中的应用。虽然功能强大,但其使用限于 Spring 应用。WebClient:也是 Spring 框架的一部分,主要用于响应式编程。同样,其使用限于 Spring 应用。
  • 易用性和灵活性JAX-RS:提供了一套简洁且灵活的接口,使得开发者可以轻松构建复杂的 HTTP 请求。支持多种请求方法和响应处理方式,且与 JAX-RS 提供者无缝集成。RestTemplate:提供了丰富的模板方法,使得发送 HTTP 请求变得简单。WebClient:支持响应式编程模型,提供了流式 API,使得异步和非阻塞操作更加自然和高效。但学习曲线较陡峭
  • 异步支持JAX-RS:支持异步请求,对于耗时的任务,提高应用性能和响应性。RestTemplate:主要支持同步操作,虽然可以通过 AsyncRestTemplate 实现异步请求,但已经不推荐使用,过时了。WebClient:天然支持异步和响应式编程,非常适合处理高并发和延迟敏感的应用。 

接下来,我们将详细介绍JAX-RS 使用Client API实现三方接口的调用。

2. 实战案例

2.1 准备三方接口

@GetMapping("/{id}")
public User queryUser(@PathVariable Long id) {
  return new User(id, "姓名 - " + new Random().nextInt(100000)) ;
}


@GetMapping("/header")
public String header(@RequestHeader("x-token") String token) {
  return token ;
}


@GetMapping("/exception")
public User exception(Long id, String name) {
  System.out.println(1 / 0) ;
  return new User(id, name) ;
}


@GetMapping("")
public List<User> queryUsers() throws Exception {
  TimeUnit.SECONDS.sleep(2);
  return List.of(
      new User(1L, "姓名 - " + new Random().nextInt(100000)),
      new User(2L, "姓名 - " + new Random().nextInt(100000)),
      new User(3L, "姓名 - " + new Random().nextInt(100000))) ;
}

上面定义了4个接口,接下来,我们将围绕这4个接口讲解JAX-RS Client API的各种使用方法。

2.2 基本使用

Response response = ClientBuilder.newClient()
      .target("http://localhost:9100/users/666")
      .request()
      .get() ;
String ret = response.readEntity(String.class) ;
System.out.println(ret) ;

输出结果

{"id":666,"name":"姓名 - 57414"}

是不是非常的简单?

2.3 注册Provider(类型转换器)

在上面的示例中,我们以String字符串的形式获取到数据,那能不能以对象如User来获取结果呢?当然可以,如下示例:

public class JSONToObjectReader implements MessageBodyReader<Object> {


  @Override
  public boolean isReadable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
    return !type.isPrimitive() ;
  }


  @Override
  public Object readFrom(Class<Object> type, Type genericType, Annotation[] annotations, MediaType mediaType,
      MultivaluedMap<String, String> httpHeaders, InputStream entityStream)
      throws IOException, WebApplicationException {
    return new ObjectMapper().readValue(entityStream, type) ;
  }
}

这里,我们自定义了 MessageBodyReader 这样我们就可以实现自己的逻辑如何进行数据的转换了,我们这里直接将 Stream 通过Jackson转换为对象。接下来就是将上面的转换器进行注册

Response response = ClientBuilder.newClient()
    // 注册转换器
    .register(JSONToObjectReader.class)
    // ...
User ret = response.readEntity(User.class) ;
System.out.println(ret) ;

其实上面的注册我们还可以全局注册,如下方式:

ClientBuilder builder = ClientBuilder.newBuilder() ;
builder.register(JSONToObjectReader.class);
Client client = builder.client() ;
// ...

这样所有三方接口调用都通过该Client即可。

2.4 注册Filter组件

如果你想在请求发送前进行修改请求数据,或者返回结果后进行相应的处理,那么你可以自定义ClientRequestFilter,ClientResponseFilter。

public class LoggingFilter implements ClientRequestFilter, ClientResponseFilter {


  private final Logger logger = LoggerFactory.getLogger(getClass()) ;
  
  @Override
  public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext) throws IOException {
    logger.info("已经得到响应结果, 响应header: {}", responseContext.getHeaders()) ;
  }


  @Override
  public void filter(ClientRequestContext requestContext) throws IOException {
    logger.info("准备发送请求, 请求Headers: {}", requestContext.getHeaders()) ;
    // 我们可以在这里进行修改请求的数据(你还可以修改请求的uri等)
    requestContext.getHeaders().replace("x-token", List.of("88888888888")) ;
  }
}

接下来,就是注册该过滤器,与上面注册转换器是一样的。

Response response = client
  .target("http://localhost:9100/users/header")
  .register(LoggingFilter.class)
  // ...

控制台输出

图片图片

2.5 定义组件顺序

如上的示例,如果我们还有一个AuthFilter,希望AuthFilter先执行,那么我们就可以通过 @Provider 注解控制他们的执行顺序。

@Priority(-1)
public class AuthFilter implements 
    ClientRequestFilter, ClientResponseFilter {}
    
@Priority(0)
public class LoggingFilter implements 
  ClientRequestFilter, ClientResponseFilter {}

运行结果

图片图片

2.6 异步调用

要使用异步调用,我们可以使用默认的线程池,也可以自定义线程池,如下自定义线程池。

int core = Runtime.getRuntime().availableProcessors() ;
ExecutorService executorService = new ThreadPoolExecutor(core, core, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100)) ;
// 配置异步线程池
ClientBuilder builder = ClientBuilder.newBuilder().executorService(executorService) ;
Client client = builder.build().register(JSONToObjectReader.class) ;


Future<User> future = client
  .target("http://localhost:9100/users/666")
  .request(MediaType.APPLICATION_JSON)
  // 设置异步调用
  .async()
  .get(new InvocationCallback<User>() {
    @Override
    public void completed(User response) {
      System.out.printf("%s - 请求完成: %s%n", Thread.currentThread().getName(), response) ;
    }
    @Override
    public void failed(Throwable throwable) {
      System.err.printf("请求失败: %s%n", throwable.getMessage()) ;
    }
  }) ;
User ret = future.get() ;
System.out.printf("返回结果: %s%n", ret) ;

运行结果

pool-1-thread-1 - 请求完成: User [id=666, name=姓名 - 34158]
返回结果: User [id=666, name=姓名 - 34158]

大多数情况你可以直接在回调的completed方法中执行其它逻辑。

2.7 反应式支持

在上面的异步请求中,我们通过回调的机制来获取结果。回调适用于简单的场景,但在多个事件同时存在时,源代码会变得更难理解。例如,当异步调用需要组合、合并或以任何方式操作时,这些场景可能导致回调嵌套在其他回调中,从而使代码的可读性大大降低——这种情况通常被称为“回调地狱”,因为调用的嵌套性质。

为了提高可读性并使程序员能够更好地理解异步计算,Java 8 引入了一个新的接口 CompletionStage,该接口包含大量专门用于管理异步计算的方法。

JAX-RS 2.1 定义了一种新的调用者类型 RxInvoker,以及基于 Java 8 的 CompletionStage 类型的默认实现 CompletionStageRxInvoker。

CompletionStage<User> stage = ClientBuilder.newClient()
    .target("http://localhost:9100/users/666")
    .register(JSONToObjectReader.class)
    .request(MediaType.APPLICATION_JSON)
    .rx()
    .get(User.class) ;
stage.whenComplete((res, ex) -> {
  if (ex != null) {
    System.err.printf("发生错误: %s%n", ex.getMessage()) ;
  } else {
    System.out.println(res) ;
  }
}) ;

这里我们可以利用CompletionState完成更多复杂的操作。有关CompletionState更多的用法,查看下面这篇文章:

强大的异步任务处理类CompletableFuture使用详解

2.8 更多的配置

如果你需要进行更多的精细配置,如:超时时间,我们可以通过如下的方式进行配置:

ClientBuilder builder = ClientBuilder.newBuilder() ;
builder.connectTimeout(1000, TimeUnit.MILLISECONDS) ;
builder.readTimeout(1000, TimeUnit.MILLISECONDS) ;
builder.register(JSONToObjectReader.class) ;
Client client = builder.build() ;
client.target("http://localhost:9100/users")
  .register(JSONToObjectReader.class)
  .request(MediaType.APPLICATION_JSON)
  .buildGet()
  .submit(new InvocationCallback<List<User>>() {
    @Override
    public void completed(List<User> response) {
      System.out.printf("返回结果: %s%n", response) ;
    }
    @Override
    public void failed(Throwable throwable) {
      System.err.printf("发生错误: %s%n", throwable.getMessage()) ;
    }
  }) ;

运行结果

发生错误: java.net.SocketTimeoutException: Read timed out

异常处理也是非常的便捷。

责任编辑:武晓燕 来源: Spring全家桶实战案例源码
相关推荐

2020-05-29 17:21:33

神经网络学习函数

2010-03-10 18:42:30

Python性能

2023-12-06 07:13:16

RESTAPI客户端

2021-09-06 08:59:00

程序员技能开发

2019-02-26 10:57:54

消息签名算法

2012-04-09 13:35:10

Instagram

2020-09-25 08:10:55

Rust系统编程

2021-11-18 11:23:09

微信WhatsAppAPP

2019-01-15 17:50:18

存储技术容器

2023-06-06 09:03:06

InnodbMySQL

2020-09-22 15:29:03

UnixC++C

2017-07-26 10:21:46

DockerLinux容器

2020-06-02 19:14:59

Kubernetes容器开发

2020-11-05 10:50:09

物联网数据技术

2022-06-01 23:27:38

区块链加密货币数字资产

2020-06-19 14:55:11

Kubernetes容器技术

2019-08-13 21:49:54

编程中文API中文编程

2014-12-19 09:59:50

代码

2022-11-28 09:00:03

编程bug开发

2023-04-10 15:41:35

点赞
收藏

51CTO技术栈公众号