在 Spring 项目里用 RestTemplate 调外部接口,本来是个很日常的需求,但一旦遇到 PATCH 方法,就很容易踩坑。
这篇笔记整理下从报错到解决的完整过程,方便自己和同事以后少掉坑。
一、问题场景与报错现象
业务需要调用外部接口:
- URL 形如:
https://example.com/api/v1/resource/{id} - HTTP 方法:
PATCH - 请求体:JSON
代码大致是这样的:
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.PATCH,
requestEntity,
String.class
);
结果一跑就报:
java.net.ProtocolException: Invalid HTTP method: PATCH
说明根本还没走到业务逻辑,底层 HTTP 客户端就把 PATCH 判定为非法方法了。
二、根本原因:默认 RestTemplate 实现不支持 PATCH
Spring 的 RestTemplate 只是一个客户端封装,真正发请求的是底层 ClientHttpRequestFactory。
如果你什么都不配置,Spring 会默认使用:
SimpleClientHttpRequestFactory→ 底层是 JDK 自带的HttpURLConnection
而 HttpURLConnection 在部分 JDK 版本/实现里只支持以下方法:
GETPOSTHEADOPTIONSPUTDELETETRACE
不支持 PATCH,于是就抛出了 java.net.ProtocolException: Invalid HTTP method: PATCH。
所以,问题根源不是 RestTemplate 本身,而是:
底层的 HTTP 实现选错了,换成支持 PATCH 的实现即可。
三、解决思路:切换到底层 Apache HttpClient(支持 PATCH)
思路很简单:
- 继续用
RestTemplate,不改业务调用姿势; - 把底层的
ClientHttpRequestFactory换成基于 Apache HttpClient 的实现; - 这样
HttpMethod.PATCH就会被正确处理。
Spring 已经提供了对应的工厂实现:
HttpComponentsClientHttpRequestFactory
它会使用 Apache HttpClient 作为底层,实现对更多 HTTP 方法的支持,包括PATCH。
四、具体实现步骤
1. 引入 Apache HttpClient 依赖(以 HttpClient5 为例)
pom.xml 中添加依赖(不暴露业务项目名称,仅给出最小依赖片段):
<!-- Apache HttpClient5: 让 RestTemplate 支持 PATCH 等方法 -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
<version>5.2.1</version>
</dependency>
版本号可以根据你项目实际情况调整,注意和 Spring Boot 版本的兼容性。
2. 配置 RestTemplate 使用 HttpComponentsClientHttpRequestFactory
新建或修改统一的 RestTemplate 配置类,例如:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.web.client.RestTemplate;
import java.nio.charset.StandardCharsets;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(ClientHttpRequestFactory factory) {
RestTemplate restTemplate = new RestTemplate(factory);
// 支持 UTF-8 文本(特别是中文)
restTemplate.getMessageConverters()
.set(1, new StringHttpMessageConverter(StandardCharsets.UTF_8));
return restTemplate;
}
@Bean
public ClientHttpRequestFactory clientHttpRequestFactory() {
// 使用 HttpComponentsClientHttpRequestFactory(底层 Apache HttpClient 支持 PATCH)
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(15000);
factory.setReadTimeout(30000);
// bufferRequestBody 默认就是 true,一般不需要改动
return factory;
}
}
关键点:
RestTemplate的 Bean 仍然通过注入ClientHttpRequestFactory创建;ClientHttpRequestFactory返回HttpComponentsClientHttpRequestFactory;- 超时可按项目实际做统一设置;
- 默认
bufferRequestBody = true,对于大部分 JSON 请求体场景都 OK。
五、业务调用示例:JSON + PATCH
有了上面的配置之后,就可以在任何地方注入 RestTemplate 并愉快地使用 PATCH。
1. 构建 JSON 请求体
import org.json.JSONObject;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
public class SomeService {
private final RestTemplate restTemplate;
public SomeService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String updateResource(Integer id, String name) {
String url = "https://example.com/api/v1/resources/" + id;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setBearerAuth("your-access-token");
JSONObject jsonBody = new JSONObject();
jsonBody.put("name", name);
jsonBody.put("description", "some description");
HttpEntity<String> requestEntity =
new HttpEntity<>(jsonBody.toString(), headers);
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.PATCH,
requestEntity,
String.class
);
return response.getBody();
}
}
只要底层工厂已切到 HttpComponentsClientHttpRequestFactory,这段代码就不会再抛 Invalid HTTP method: PATCH。
六、几个容易忽略的小点
- 必须重启应用
配置类和依赖修改之后,需要重新启动应用让新的 Bean 配置生效。 - 确认没有多个 RestTemplate Bean 冲突
如果项目里有多个RestTemplate定义,要确认业务使用的是你配置好的那个。 - 请求体大小与 bufferRequestBody
默认是启用缓冲(bufferRequestBody = true),适合大多数 JSON 接口;
若改为false会走 Streaming 实现,部分调用方式(尤其是自定义RequestCallback时调用getBody())会抛出UnsupportedOperationException,一般不建议轻易关闭。
七、小结
整个问题的来龙去脉可以概括为三句话:
RestTemplate默认基于HttpURLConnection,不支持PATCH,导致Invalid HTTP method: PATCH。- 解决办法是替换底层实现为 Apache HttpClient,对应 Spring 的
HttpComponentsClientHttpRequestFactory。 - 配好 pom.xml 依赖 + RestTemplateConfig 之后,直接使用
HttpMethod.PATCH加 JSON 请求体即可。
以后再遇到 RestTemplate + PATCH 报 ProtocolException,就可以直接按这套方案检查:
- 是否引入 HttpClient 依赖?
RestTemplate是否使用HttpComponentsClientHttpRequestFactory?- 应用是否已经重启让配置生效?
文章评论