1. 概述

在通过REST API更新对象时,使用PATCH方法是一种好实践。这样我们可以仅针对我们希望更改的字段进行部分更新。如果需要完全替换现有资源,也可以使用PUT方法。

本教程将指导如何在OpenFeign中设置HTTP PATCH方法。我们将探讨在Feign客户端测试PATCH方法时遇到的一个意外错误,并理解其根本原因并修复它。

2. 示例应用(Spring Boot)

设想我们需要构建一个简单的微服务,调用下游服务执行部分更新。

2.1. Maven依赖项

首先,我们需要添加spring-boot-starter-webspring-cloud-starter-openfeign的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.2. 实现Feign客户端

现在,让我们使用Spring的Web注解在Feign中实现PATCH方法。

首先,定义一个包含一些属性的User类:

public class User {
    private String userId;
    private String userName;
    private String email;
}

接下来,我们实现UserClient接口及其updateUser方法:

@FeignClient(name = "user-client", url = "http://localhost:8082/api/user")
public interface UserClient {
    @RequestMapping(value = "{userId}", method = RequestMethod.PATCH)
    User updateUser(@PathVariable(value = "userId") String userId, @RequestBody User user);
}

在这个PATCH方法中,我们只传递包含所需更新字段的User对象以及userId字段。这比发送整个资源表示更简洁,节省网络带宽,并且在多个字段上对同一对象进行多处更新时避免了竞争。

相比之下,如果我们使用PUT请求,就需要传递完整的资源表示来替换现有资源。

3. Feign客户端的测试实现

现在,我们来为UserClient实现一个测试用例,通过模拟HTTP调用来进行。

3.1. 设置WireMock服务器

为了实验,我们需要使用一个模拟框架来模拟我们正在调用的服务。

首先,添加WireMockServer的Maven依赖:

<dependency>
    <groupId>com.github.tomakehurst</groupId>
    <artifactId>wiremock-jre8</artifactId>
    <version>2.35.0</version>
    <scope>test</scope>
</dependency>

然后,配置并启动WireMockServer

WireMockServer wireMockServer = new WireMockServer(8082);
configureFor("localhost", 8082);
wireMockServer.start();

WireMockServer将以Feign客户端配置的主机和端口启动。

3.2. 模拟PATCH API

我们将模拟PATCH方法来测试更新User API:

String updatedUserResponse = "{\n" +
    "\"userId\": 100001,\n" +
    "\"userName\": \"name\",\n" +
    "\"email\": \"[email protected]\"\n" +
    "}";
stubFor(patch(urlEqualTo("/api/user/".concat(USER_ID)))
  .willReturn(aResponse().withStatus(HttpStatus.OK.value())
  .withHeader("Content-Type", "application/json")
  .withBody(updatedUserResponse)));

3.3. 测试PATCH请求

现在,我们将User对象及其所需的更新字段传递给UserClient进行测试。

接着,完成测试并验证更新功能:

User user = new User();
user.setUserId("100001");
user.setEmail("[email protected]");
User updatedUser = userClient.updateUser("100001", user);

assertEquals(user.getUserId(), updatedUser.getUserId());
assertEquals(user.getEmail(), updatedUser.getEmail());

预期这个测试应该通过。然而,我们会收到Feign客户端的一个意外错误:

feign.RetryableException: Invalid HTTP method: PATCH executing PATCH http://localhost:8082/api/user/100001
        at feign.FeignException.errorExecuting(FeignException.java:268)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:131)
    at feign.SynchronousMethodHandler.invoke(SynchronousMethodHandler.java:91)
    at feign.ReflectiveFeign$FeignInvocationHandler.invoke(ReflectiveFeign.java:100)
    at jdk.proxy2/jdk.proxy2.$Proxy80.updateUser(Unknown Source)
    at com.baeldung.cloud.openfeign.patcherror.client.UserClientUnitTest.givenUserExistsAndIsValid_whenUpdateUserCalled_thenReturnSuccess(UserClientUnitTest.java:64)
    ...

接下来,我们将详细探究这个错误。

3.4. HTTP方法无效错误的原因

上述错误信息表明请求的HTTP方法无效。尽管根据HTTP标准,PATCH方法是有效的。

从错误消息中可以看出,问题是由ProtocolException类引起的,并由HttpURLConnection类传播:

Caused by: java.net.ProtocolException: Invalid HTTP method: PATCH
    at java.base/java.net.HttpURLConnection.setRequestMethod(HttpURLConnection.java:489)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.setRequestMethod(HttpURLConnection.java:598)
    at feign.Client$Default.convertAndSend(Client.java:170)
    at feign.Client$Default.execute(Client.java:104)
    at feign.SynchronousMethodHandler.executeAndDecode(SynchronousMethodHandler.java:119)

事实证明,默认的HTTP客户端使用HttpURLConnection类建立HTTP连接。HttpURLConnection有一个setRequestMethod方法来设置请求方法。

不幸的是,HttpURLConnection类并不将PATCH方法识别为有效类型。

4. 解决PATCH方法错误

要解决这个问题,我们需要添加一个支持的HTTP客户端依赖项。同时,我们将通过添加配置文件来覆盖默认的HTTP客户端。

4.1. 添加OkHttpClient依赖

请添加feign-okhttp依赖:

<dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
</dependency>

我们需要注意,其他任何支持的HTTP客户端,如ApacheHttpClient,也同样适用。

4.2. 启用OkHttpClient

OkHttpClient类认为PATCH方法是有效的类型,不会抛出异常。

让我们通过以下配置启用OkHttpClient

feign.okhttp.enabled=true

最后,重新运行测试并验证PATCH方法是否工作。现在,Feign客户端不再抛出任何错误:

UserClientUnitTest.givenUserExistsAndIsValid_whenUpdateUserCalled_thenReturnSuccess: 1 total, 1 passed

5. 总结

在这篇文章中,我们学习了如何在OpenFeign中使用PATCH方法。我们也看到了测试时的一个意外错误,并理解了其根本原因,并进行了修复。

我们还通过OkHttpClient的实现解决了问题。

如往常一样,示例代码可在GitHub上找到。