读书笔记《building-a-restful-web-service-with-spring》构建REST客户端
客户端库应该是自包含和可移植的,以便 RESTful Web 服务的消费者可以使用它们。在服务器端和客户端 端之间共享代码会很诱人。但是,这样做会使客户端和服务器代码紧密耦合并阻碍客户端库的可移植性。
在我们的示例属性管理服务的上下文中,我们将客户端库构建为一个新的 Maven 模块。该模块将需要以下依赖项:
要使用 Spring 构建 RESTful 客户端,我们至少需要对 Spring 的 web 模块的依赖。此外,Spring 在运行时需要 Apache commons-logging
库。最后,我们将利用 Jackson 来执行我们的 Java 类与其 JSON 表示之间的数据绑定。
有了这些 依赖项,让我们直接开始为我们的示例物业管理系统构建一个客户端。我们将从 Inventory
组件开始,使用以下(简化的)客户端界面:
该接口允许通过其标识符检索房间。
我们现在可以开始实现我们的客户端:
Spring 为我们提供的主要类是 org.springframework.web.client.RestTemplate
。这个类允许我们使用 RestTemplate.exchange()
对后端服务器进行 HTTP 调用。
正如第4章中所讨论的,数据表示,我们的服务响应以通用信封格式包装,其中包含有关请求状态的信息。由于所有响应都将具有相同的格式,我们可以创建一个实用方法来以一致的方式处理它们。这就是 ResponseHandler.handle()
的目的。
由于我们使用 通用类型来指定要包含的有效负载响应的类型,因此我们需要将该信息传递给 Spring,以便它可以正确提取数据。这是通过声明 ParameterizedTypeReference
来实现的。
正如我们之前在本章中看到的,我们的客户端是使用接口定义的。这种增加抽象级别的主要动机是我们可以用远程实现 代替本地实现。假设组件 A 依赖于组件 B。如果这些组件部署在不同的服务器上,我们将希望使用 B 的客户端实现远程调用组件。但是,如果两个组件共存于同一个 JVM 中,则使用远程客户端会导致不必要的网络延迟。将客户端替换为直接调用组件 B 的 Java 实现的客户端可确保不会发生网络连接,从而减少延迟。
Note
这种模式通常用于微服务架构(https://en.wikipedia.org/wiki/Microservices),它们变得非常流行。这种架构风格提倡将复杂的应用程序分解成小的独立组件。
让我们看一下示例物业管理系统的可用性和预订组件。可用性服务取决于 预订服务。为了解决这个问题,我们为预订服务定义了一个简单的客户端接口,如下所示:
这个客户端接口定义了一个单一的方法来通过它的标识符来查找一个预订。
我们的第一个部署方法是让两个组件在不同的 JVM 中运行。因此,我们实现了可用性服务可以利用的远程客户端:
在本章的前面部分,我们已经介绍了如何使用 org.springframework.web.client.RestTemplate
来远程调用服务。
现在,为了减少 延迟,我们决定在同一个JVM 中运行两个 组件.使用此客户端实现将抵消在同一进程中托管两种服务的任何好处。因此,我们需要一个新的实现:
通过这个新实现,我们只需将预订检索委托给实际服务,绕过任何网络。然后,我们需要将数据转换为客户端调用者所期望的。
在第7章中,处理安全,我们 学会了将安全性应用于 RESTful 端点。例如,我们讨论了如何为预订服务设置 HTTP Basic
身份验证。我们可以扩展上一节的示例并添加安全处理。接下来的两节说明如何处理 Basic
和 Digest
身份验证。
此身份验证方案要求 Authorization
标头包含以 Base64 编码的用户名/密码 对。这很容易通过如下修改客户端来实现:
这个新的构造函数使用用户名和密码来验证客户端。然后它生成 Base64 编码的凭据,并为了将 Authorization
令牌添加到每个请求,定义一个新的 org.springframework.http。 client.ClientHttpRequestInterceptor
。拦截器以适当的格式添加标头并允许执行请求。
对于这个认证方案,我们来看看开发者如何选择Apache的HttpClient
(https://hc.apache.org ) 作为底层 HTTP 客户端框架。为此,我们需要在我们的项目中添加以下依赖项:
而且,我们改变了创建 RestTemplate
实例的方式:
通过将请求工厂传递给我们的模板,我们能够利用 HttpClient
来管理 Digest
身份验证。我们需要创建一个HttpComponentsClientHttpRequestFactory
的扩展,如下:
此类创建一个 HTTP 上下文,该上下文设置为存储 Digest
凭据。我们通过创建 org 的新实例来指示
。通过这种设置,我们的客户端类透明地处理身份验证,其方式与上一节中处理 HttpClient
使用 Digest
方案处理身份验证。 apache.http.impl.auth.DigestSchemeBasic
身份验证方案的方式相同。
第 3 章,第一个端点,触及定义一致的响应格式,包括错误代码和错误描述。客户端的推论是定义一个异常, 封装有关特定调用失败原因的信息。因此,我们可以定义以下异常:
使用具有此类异常处理的 RESTful Web 服务的客户端将具有一致的方式来管理和报告错误。但是,在某些情况下,通用异常处理需要在客户端进行更多工作。例如,当服务器无法处理当前负载并要求客户端在延迟几秒钟后重新发出请求时,它可能会生成特定错误。
在这种情况下,如果服务消费者能够捕获特定异常,而不是必须自省一般异常,那么他们的工作就会变得更轻松。客户端实现可以声明一个ClientException
的扩展,例如,ServerOverloadedClientException
,并在相关的地方抛出此异常而不是通用异常。
细心的读者会注意到我们将 ClientException
声明为未经检查的异常。这使我们能够管理我们的异常处理而不暴露它。