Grails 及 Micronaut HTTP 客户端
在本指南中,我们将学习如何在 Grails 应用中使用 Micronaut HTTP 客户端。
作者:尼拉夫·阿萨尔
Grails 版本 5.0.1
1 Grails 培训
Grails 培训 - 由创建和积极维护 Grails 框架的人员开发和提供!。
2 开始
在本指南中,你将学习如何执行 HTTP 客户端,以进行外部 REST API 调用,以及执行 spock 测试。你将使用 Micronaut HTTP 客户端,它同时具有低级别 API 和更高级别 AOP 驱动的 API。
2.1 你需要
要完成本指南,你需要具备以下条件
-
一些时间
-
一个合适的文本编辑器或 IDE
-
已安装的 JDK 1.8 或更高版本,并已适当配置
JAVA_HOME
2.2 如何完成指南
要开始,请执行以下操作
-
下载并解压缩源代码
或
-
克隆 Git 代码库
git clone https://github.com/grails-guides/grails-micronaut-http.git
Grails 指南代码库包含两个文件夹
-
initial
初始项目。通常是一个简单的 Grails 应用,带有其他一些代码,让你轻松上手。 -
complete
一个完成的示例。它是按照指南中提供的步骤操作,并对initial
文件夹应用这些更改的结果。
要完成指南,请转到 initial
文件夹
-
cd
进入grails-guides/grails-micronaut-http/initial
并按照下一部分中的说明进行操作。
如果你 cd 进入 grails-guides/grails-micronaut-http/complete ,则可以直接进入完成的示例 |
3 编写应用程序
/initial
文件夹是使用 rest-api
配置文件创建的 grails 应用程序。这是一个查询 Apple iTunes API 的简单应用程序。该应用程序接收 searchTerm
,并分层为
-
example.grails.SearchController
-
example.grails.ItunesSearchService
调用被隐藏,直到我们在本指南中实施它
通过运行 ./gradlew bootRun
,然后转到 URL http://localhost:8080/search/searchWithApi?searchTerm=U2
来验证功能。
您应该看到返回的虚拟数据 JSON。
4 Micronaut HTTP 客户端
Micronaut HTTP 客户端依赖项包含在 grails rest-api
配置文件中,并替换了 datastore-rest-client
。从 Grails 4 开始,它是适合 Grails 的推荐 HTTP 客户端。(旧的 HTTP 客户端在 Grails 4 中仍向后兼容)。
在我们的音乐应用程序中,我们将在服务功能和测试中使用 Micronaut HTTP 客户端,因此我们必须将 build.gradle
中的限定词更改为 compile
dependencies {
...
implementation "io.micronaut:micronaut-http-client"
}
4.1 使用低级别 Micronaut HTTP 客户端 API
我们将查询艺术家的 iTunes 存储库,并期望以 JSON 形式接收多条信息,例如专辑名称和专辑的 URL 链接。为了轻松捕捉返回数据,我们可以创建属性名称与 JSON 结构完全相同的 POJO。ITunes API 将返回 resultCount
,其中包含包含下列内容的专辑列表
-
artistName
-
collectionName
-
collectionViewUrl
请注意,以下 POJO 已被创建
package example.grails
import groovy.transform.CompileStatic
@CompileStatic
class SearchResult {
int resultCount
List<Album> results = []
}
package example.grails
import groovy.transform.CompileStatic
@CompileStatic
class Album {
String artistName
String collectionName
String collectionViewUrl
}
我们需要配置 Micronaut 以接受 iTunes API 的 text/javascript
MIME 类型。
---
micronaut:
codec:
json:
additionalTypes:
- text/javascript
在 ItunesSearchService
中,我们创建低级别客户端并使用 API 联系 iTunes.apple.com
。从概念上讲,我们将使用 baseUrl
创建一个客户端,然后形成一个发送带有 URL 参数的 GET
请求的请求对象。随后,我们将发出阻塞请求并接收一个 String 响应。来自 Jackson Databind 的 ObjectMapper
将传入的 JSON 映射到 POJO。将以下内容添加到 ItunesSearchService
List<Album> searchWithApi(String searchTerm) {
String baseUrl = "https://itunes.apple.com/"
HttpClient client = HttpClient.create(baseUrl.toURL())
HttpRequest request = HttpRequest.GET(UriBuilder.of('/search')
.queryParam('limit', 25)
.queryParam('media', 'music')
.queryParam('entity', 'album')
.queryParam('term', searchTerm)
.build())
HttpResponse<String> resp = client.toBlocking().exchange(request, String) (1)
String json = resp.body()
ObjectMapper objectMapper = new ObjectMapper()
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) (2)
SearchResult searchResult = objectMapper.readValue(json, SearchResult) (3)
searchResult.results
}
1 | 发出具有阻塞调用的请求,并接受 String 响应。 |
2 | 忽略额外的属性。 |
3 | 将 JSON 映射到 POJO 中。 |
运行 ./gradlew bootRun
及点击 http://localhost:8080/search/searchWithApi?searchTerm=U2
,然后查看结果。
4.2 使用声明声明 Micronaut HTTP 客户端
可通过 Micronaut 的声明式 HTTP 客户端实现相同的功能。可以在接口中声明 @Client
注解,并在编译时为您创建一个客户端。已通过 @Get
声明的界面方法并返回数据绑定的 POJO。这种简单且优雅的方法消除了先前示例中的大部分代码。
创建声明式客户端
package example.grails
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
@Client("https://itunes.apple.com/") (1)
interface ItunesClient {
@Get("/search?limit=25&media=music&entity=album&term={term}") (2)
SearchResult search(String term)
}
1 | 使用 @Client 声明并设置 URL。 |
2 | 使用参数定义 @Get 请求。 |
将客户端注入到服务中,然后向界面方法添加调用。
...
@Autowired
ItunesClient itunesClient
List<Album> searchWithDeclarativeClient(String searchTerm) {
SearchResult searchResult = itunesClient.search(searchTerm)
searchResult.results
}
...
添加一个将路由转至服务方法的控制器方法
...
def searchWithDeclarativeClient(String searchTerm) {
if(searchTerm) {
List<Album> albums = itunesSearchService.searchWithDeclarativeClient(searchTerm)
respond([searchTerm: searchTerm, albums: albums])
}
}
...
运行 ./gradlew bootRun
,然后单击 http://localhost:8080/search/searchWithDeclarativeClient?searchTerm=U2 并查看结果。
5 使用 Micronaut HTTP 客户端进行测试
Micronaut 客户端还可用于使用 Spock 进行测试。在我们的音乐应用程序中,让我们创建一个域对象来获取唱片公司。我们将使用 grails 内置的 域类的 REST API。
创建一个域对象,并使用 @Resource
和给定的 URI 对其进行标记。这将公开对象的 REST 功能。
package example.grails
import grails.rest.Resource
@Resource(uri='/recordlabels')
class RecordLabel {
String name
}
在 BootStrap.groovy
中,为测试目的添加一些种子数据
package example.grails
import grails.gorm.services.Service
@Service(RecordLabel)
interface RecordLabelService {
RecordLabel save(String name)
}
package example.grails
import groovy.transform.CompileStatic
@CompileStatic
class BootStrap {
RecordLabelService recordLabelService
def init = { servletContext ->
recordLabelService.save("Warner")
recordLabelService.save("Sony")
}
def destroy = {
}
}
让我们创建一个集成测试套件,该套件首先执行 GET
请求以检索现有 RecordLabel 列表。测试还将发出 POST
以插入一个包含所传入数据的映射的唱片公司。在两个测试中,我们将验证响应是否返回正确。
package example.grails
import grails.gorm.transactions.Rollback
import grails.testing.mixin.integration.Integration
import grails.testing.spock.OnceBefore
import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpResponse
import io.micronaut.http.HttpStatus
import io.micronaut.http.client.HttpClient
import spock.lang.Shared
import spock.lang.Specification
@Integration
@Rollback
class RecordLabelControllerSpec extends Specification {
@Shared
HttpClient client
@OnceBefore
void init() { (1)
String baseUrl = "http://localhost:$serverPort"
this.client = HttpClient.create(baseUrl.toURL())
}
void "test rest get record labels"() {
when:"record labels exist"
HttpResponse<List<Map>> resp = client.toBlocking().exchange(HttpRequest.GET("/recordlabels"), Argument.of(List, Map)) (2)
then: "client can retrieve them"
resp.status == HttpStatus.OK (3)
resp.body().size() == 2
resp.body()[0].name == "Warner"
resp.body()[1].name == "Sony"
}
void "test rest post record labels"() {
when:"a post is issued"
HttpResponse<Map> resp = client.toBlocking().exchange(HttpRequest.POST("/recordlabels", [name: "Universal"]), Map) (4)
then: "element is created"
resp.status == HttpStatus.CREATED (5)
resp.body().size() == 2
resp.body().id
resp.body().name == "Universal"
}
}
1 | 为所有测试初始化客户端一次。使用 serverPort 为其提供 URL,它是集成测试的已分配端口。 |
2 | 发出 GET 。预期的正文类型将是映射列表。 |
3 | 验证 JSON 的状态和正文元素。 |
4 | 发送数据映射到 POST ,该映射将作为 JSON 进行解释。 |
5 | 验证测试的状态和返回数据。 |
使用 ./gradlew check
测试应用程序。
6 运行应用程序
要运行应用程序,请使用 ./gradlew bootRun
命令,该命令将在 8080 端口启动应用程序。