Quarkus - 使用 REST 客户端 (Client)
本指南说明了如何使用 MicroProfile REST 客户端简单地调用 REST API。
如果需要编写服务器 JSON REST API 参考 JSON REST APIs 。 |
完整代码
我们建议按照后续各节中说明进行操作逐步创建应用程序。
但是,您也可以直接查看最终完成的例子。
克隆 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-client
和 resteasy-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
虽然 它将允许缩小本机可执行文件中包含的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 Assured 的 json-path 功能.
异步支持
Rest客户端支持异步Rest调用。
异步支持有两种形式:您可以返回 CompletionStage
或 Uni
(需要 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
生成原生可执行文件 。