Quarkus - 使用 REST 客户端 (Client)

本指南说明了如何使用 MicroProfile REST 客户端简单地调用 REST API。

如果需要编写服务器 JSON REST API 参考 JSON REST APIs

准备

要完成本指南,需要:

  • 15分钟以内

  • IDE

  • 安装好 JDK1.8+ 并正确配置了 JAVA_HOME

  • Apache Maven 3.6.2+

完整代码

我们建议按照后续各节中说明进行操作逐步创建应用程序。

但是,您也可以直接查看最终完成的例子。

克隆 Git 仓库: git clone https://github.com/quarkusio/quarkus-quickstarts.git ,或下载 压缩包

代码在 rest-client-quickstart 目录.

创建 Maven 项目

首先,我们需要一个新项目。使用以下命令创建一个新项目:

mvn io.quarkus:quarkus-maven-plugin:1.3.1.Final:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=rest-client-quickstart \
    -DclassName="org.acme.rest.client.CountriesResource" \
    -Dpath="/country" \
    -Dextensions="rest-client, resteasy-jsonb"
cd rest-client-quickstart

该命令生成了带 REST 接口的 Maven 项目,并引入 rest-clientresteasy-jsonb extensions 。

建立模型

在本指南中,我们将演示如何调用一些 restcountries.eu 提供的 REST API 。

我们的首要任务是以 Country POJO 的形式建立将要使用的模型。

创建 src/main/java/org/acme/rest/client/Country.java 文件,其内容如下:

package org.acme.rest.client;

import java.util.List;

public class Country {

    public String name;
    public String alpha2Code;
    public String capital;
    public List<Currency> currencies;

    public static class Currency {
        public String code;
        public String name;
        public String symbol;
    }
}

上面的模型只是接口提供字段的子集,但是对于本指南而言,它就足够了。

创建 interface

使用 MicroProfile REST 客户端很简单,只需要使用适当的 JAX-RS 和 MicroProfile 注解创建接口。我们这里的接口在 src/main/java/org/acme/rest/client/CountriesService.java ,其内容如下:

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.Set;

@Path("/v2")
@RegisterRestClient
public interface CountriesService {

    @GET
    @Path("/name/{name}")
    @Produces("application/json")
    Set<Country> getByName(@PathParam String name);
}

getByName 方法使我们的代码能够从 REST Countries API 中按名称查询国家。 系统会替我们处理网络、编码这些底层技术。

上面代码中注解的目的如下:

  • @RegisterRestClient 让 Quarkus 知道该接口可以作为 REST Client 通过 CDI 注入 。

  • @Path, @GET@PathParam 是标准的 JAX-RS 注解用于定义如何调用接口。

  • @Produces 定义期望的 content-type

虽然 @Consumes@Produces 是可选的,因为它支持自动协商,但强烈建议使用它们对端点(endpoint)进行注释,以精确定义所需的内容类型。

它将允许缩小本机可执行文件中包含的JAX-RS提供程序(可以视为转换器)的数量。

创建配置

为了确定被调用的 REST 接口网址 ,REST 客户端使用 application.properties 中的配置。 属性的名称需要遵循约定,最好如以下代码所示:

# Your configuration properties
org.acme.rest.client.CountriesService/mp-rest/url=https://restcountries.eu/rest # (1)
org.acme.rest.client.CountriesService/mp-rest/scope=javax.inject.Singleton # (2)
1 此项配置意味着使用 org.acme.rest.client.CountriesService 调用接口时请求都使用 https://restcountries.eu/rest 作为基础网址。 使用上述配置,用参数 France 调用 CountriesService 的方法 getByName 时 HTTP GET 请求时的完整网址是 https://restcountries.eu/rest/v2/name/France
2 此项配置意味着 org.acme.rest.client.CountriesService 的默认范围是 @Singleton 。 支持的范围(scope)值 @Singleton, @Dependent, @ApplicationScoped and @RequestScoped 。默认范围是 @Dependent 。 默认范围也可以在接口上定义。

请注意, org.acme.rest.client.CountriesService 必须 与上一节中创建的接口 CountriesService 全名一致。

为了简化配置,您也可以使用 @RegisterRestClient configKey 来使用别的配置项名称替代接口全名。

@RegisterRestClient(configKey="country-api")
public interface CountriesService {
    [...]
}
# Your configuration properties
country-api/mp-rest/url=https://restcountries.eu/rest #
country-api/mp-rest/scope=javax.inject.Singleton # /

修改 JAX-RS resource

打开 src/main/java/org/acme/rest/client/CountriesResource.java 文件改成下列内容:

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.Set;

@Path("/country")
public class CountriesResource {

    @Inject
    @RestClient
    CountriesService countriesService;


    @GET
    @Path("/name/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public Set<Country> name(@PathParam String name) {
        return countriesService.getByName(name);
    }
}

注意,注入 CountriesService 时,除了标准的CDI @Inject 注解外,还需要使用 MicroProfile 注解 @RestClient

更新测试代码

We also need to update the functional test to reflect the changes made to the endpoint. Edit the src/test/java/org/acme/rest/client/CountriesResourceTest.java file and change the content of the testCountryNameEndpoint method to:

我们还需要更新功能测试代码以反映对接口所做的更改。 编辑 src/test/java/org/acme/rest/client/CountriesResourceTest.java 文件,将方法 testCountryNameEndpoint 改为:

package org.acme.rest.client;

import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

@QuarkusTest
public class CountriesResourceTest {

    @Test
    public void testCountryNameEndpoint() {
        given()
          .when().get("/country/name/greece")
          .then()
             .statusCode(200)
             .body("$.size()", is(1),
                     "[0].alpha2Code", is("GR"),
                     "[0].capital", is("Athens"),
                     "[0].currencies.size()", is(1),
                     "[0].currencies[0].name", is("Euro")
             );
    }

}

上边代码使用了 REST Assuredjson-path 功能.

异步支持

Rest客户端支持异步Rest调用。 异步支持有两种形式:您可以返回 CompletionStageUni(需要 quarkus-resteasy-mutiny 扩展)。 让我们在 CountriesService 接口中添加一个方法 getByNameAsync 试试。该代码应如下所示:

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.concurrent.CompletionStage;
import java.util.Set;

@Path("/v2")
@RegisterRestClient
public interface CountriesService {

    @GET
    @Path("/name/{name}")
    @Produces("application/json")
    Set<Country> getByName(@PathParam String name);

    @GET
    @Path("/name/{name}")
    @Produces("application/json")
    CompletionStage<Set<Country>> getByNameAsync(@PathParam String name);
}

编辑 src/main/java/org/acme/rest/client/CountriesResource.java 改为下列内容:

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.CompletionStage;
import java.util.Set;

@Path("/country")
public class CountriesResource {

    @Inject
    @RestClient
    CountriesService countriesService;


    @GET
    @Path("/name/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public Set<Country> name(@PathParam String name) {
        return countriesService.getByName(name);
    }

    @GET
    @Path("/name-async/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public CompletionStage<Set<Country>> nameAsync(@PathParam String name) {
        return countriesService.getByNameAsync(name);
    }
}

为了测试异步方法, 在 CountriesResourceTest 添加测试方法:

@Test
public void testCountryNameAsyncEndpoint() {
    given()
    .when().get("/country/name-async/greece")
    .then()
        .statusCode(200)
        .body("$.size()", is(1),
             "[0].alpha2Code", is("GR"),
             "[0].capital", is("Athens"),
             "[0].currencies.size()", is(1),
             "[0].currencies[0].name", is("Euro")
        );
    }

Uni 版的也是类似:

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import java.util.concurrent.CompletionStage;
import java.util.Set;

@Path("/v2")
@RegisterRestClient
public interface CountriesService {

    // ...

    @GET
    @Path("/name/{name}")
    @Produces("application/json")
    Uni<Set<Country>> getByNameAsUni(@PathParam String name);
}

CountriesResource 改为:

package org.acme.rest.client;

import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.jboss.resteasy.annotations.jaxrs.PathParam;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.concurrent.CompletionStage;
import java.util.Set;

@Path("/country")
public class CountriesResource {

    @Inject
    @RestClient
    CountriesService countriesService;


    // ...

    @GET
    @Path("/name-uni/{name}")
    @Produces(MediaType.APPLICATION_JSON)
    public Uni<Set<Country>> nameAsync(@PathParam String name) {
        return countriesService.getByNameAsUni(name);
    }
}
Mutiny

上边代码使用了 Mutiny 响应式类型, 如果不了解请先参阅 Getting Started with Reactive guide .

打包运行应用程序

使用以下命令运行应用程序 ./mvnw compile quarkus:dev 。 打开浏览器访问 http://localhost:8080/country/name/greece

您应该看到一个 JSON 对象,其中包含有关 Greece 的一些基本信息。

像往常一样,可以用 ./mvnw clean package 打包,然后使用可以执行 -runner.jar 。 您也可以使用 ./mvnw clean package -Pnative 生成原生可执行文件 。

quarkus.pro 是基于 quarkus.io 的非官方中文翻译站 ,最后更新 2020/04 。
沪ICP备19006215号-8
QQ交流群:1055930959
微信群: