RestTemplate是一个用于发送HTTP请求并获取响应的客户端工具
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- 项目模型版本 -->
<modelVersion>4.0.0</modelVersion>
<!-- 项目坐标 -->
<groupId>local.ateng.java</groupId>
<artifactId>rest-template-jdk8</artifactId>
<version>v1.0</version>
<name>rest-template-jdk8</name>
<description>RestTemplate是一个用于发送HTTP请求并获取响应的客户端工具</description>
<!-- 项目属性 -->
<properties>
<java.version>8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.7.18</spring-boot.version>
<maven-compiler.version>3.12.1</maven-compiler.version>
<lombok.version>1.18.36</lombok.version>
<fastjson2.version>2.0.53</fastjson2.version>
</properties>
<!-- 项目依赖 -->
<dependencies>
<!-- Spring Boot Web Starter: 包含用于构建Web应用程序的Spring Boot依赖项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Test: 包含用于测试Spring Boot应用程序的依赖项 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Lombok: 简化Java代码编写的依赖项 -->
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
<!-- 高性能的JSON库 -->
<!-- https://github.com/alibaba/fastjson2/wiki/fastjson2_intro_cn#0-fastjson-20%E4%BB%8B%E7%BB%8D -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>${fastjson2.version}</version>
</dependency>
<!-- Apache HttpComponents客户端 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
</dependencies>
<!-- Spring Boot 依赖管理 -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 插件仓库配置 -->
<repositories>
<!-- Central Repository -->
<repository>
<id>central</id>
<name>阿里云中央仓库</name>
<url>https://maven.aliyun.com/repository/central</url>
<!--<name>Maven官方中央仓库</name>
<url>https://repo.maven.apache.org/maven2/</url>-->
</repository>
</repositories>
<!-- 构建配置 -->
<build>
<finalName>${project.name}-${project.version}</finalName>
<plugins>
<!-- Maven 编译插件 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler.version}</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
<encoding>${project.build.sourceEncoding}</encoding>
<!-- 编译参数 -->
<compilerArgs>
<!-- 启用Java 8参数名称保留功能 -->
<arg>-parameters</arg>
</compilerArgs>
</configuration>
</plugin>
<!-- Spring Boot Maven 插件 -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<!-- 第一个资源配置块 -->
<resource>
<directory>src/main/resources</directory>
<filtering>false</filtering>
</resource>
<!-- 第二个资源配置块 -->
<resource>
<directory>src/main/resources</directory>
<includes>
<include>application*</include>
<include>bootstrap*.yml</include>
<include>common*</include>
<include>banner*</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
</build>
</project>package local.ateng.java.rest.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置文件
*
* @author 孔余
* @email 2385569970@qq.com
* @since 2025-02-02
*/
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}@SpringBootTest
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class RestTemplateApplicationTests {
private final RestTemplate restTemplate;
}getForObject方法,获取响应体,将其转换为第二个参数指定的类型
@Test
void getForObject() {
//getForObject方法,获取响应体,将其转换为第二个参数指定的类型
JSONObject jsonObject = restTemplate.getForObject("https://www.wanandroid.com/article/list/0/json", JSONObject.class);
String string = JSON.toJSONString(jsonObject, JSONWriter.Feature.PrettyFormat);
System.out.println(string);
}getForEntity方法,返回值为ResponseEntity类型
@Test
void getForEntity() {
//getForEntity方法,返回值为ResponseEntity类型
// ResponseEntity中包含了响应结果中的所有信息,比如头、状态、body
ResponseEntity<JSONObject> response = restTemplate.getForEntity("https://www.wanandroid.com/article/list/0/json", JSONObject.class);
// 获取状态对象
HttpStatusCode httpStatusCode = response.getStatusCode();
System.out.println("StatusCode=" + httpStatusCode);
// 获取状态码
int statusCodeValue = response.getStatusCodeValue();
System.out.println("StatusCodeValue=" + statusCodeValue);
// 获取headers
HttpHeaders httpHeaders = response.getHeaders();
System.out.println("Headers=" + httpHeaders);
// 获取body
JSONObject body = response.getBody();
System.out.println(body);
}url中有动态参数
@Test
void getForObject2() {
//url中有动态参数
String url = "https://www.wanandroid.com/article/list/{id}/json";
HashMap<String, String> map = new HashMap<>();
map.put("id", "3");
JSONObject jsonObject = restTemplate.getForObject(url, JSONObject.class, map);
System.out.println(jsonObject);
}查询参数
@Test
void getForObjectWithQueryParams() {
// url中有动态路径参数
String url = "https://www.wanandroid.com/article/list/{id}/json";
// 设置路径参数
HashMap<String, String> pathParams = new HashMap<>();
pathParams.put("id", "3");
// 使用UriComponentsBuilder添加查询参数
String finalUrl = UriComponentsBuilder.fromHttpUrl(url)
.queryParam("page", 1)
.queryParam("limit", 10)
.encode() // 强制使用UTF-8编码
.toUriString();
System.out.println(finalUrl);
// 发起请求并获取响应体
JSONObject jsonObject = restTemplate.getForObject(finalUrl, JSONObject.class, pathParams);
System.out.println(jsonObject);
} @Test
void getForObject3() {
// get请求,带请求头的方式,使用exchange
String url = "https://www.wanandroid.com/article/list/{id}/json";
// 请求头
HttpHeaders headers = new HttpHeaders();
headers.add("header-1", "V1");
headers.add("header-2", "Spring");
headers.add("header-3", "SpringBoot");
// url的参数
HashMap<String, String> uriVariables = new HashMap<>();
uriVariables.put("id", "3");
// HttpEntity:HTTP实体,内部包含了请求头和请求体
HttpEntity requestEntity = new HttpEntity(
null,//body部分,get请求没有body,所以为null
headers //头
);
// 使用exchange发送请求
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(
url, //url
HttpMethod.GET, //请求方式
requestEntity, //请求实体(头、body)
JSONObject.class,//返回的结果类型
uriVariables //url中的占位符对应的值
);
System.out.println(responseEntity.getStatusCodeValue());
System.out.println(responseEntity.getHeaders());
System.out.println(responseEntity.getBody());
}发送form-data表单post请求
@Test
void postForObject() {
// 发送form-data表单post请求
String url = "https://api.apiopen.top/api/login";
//①:表单信息,需要放在MultiValueMap中,MultiValueMap相当于Map<String,List<String>>
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
//调用add方法填充表单数据(表单名称:值)
body.add("account", "309324904@qq.com");
body.add("password", "123456");
//②:发送请求(url,请求体,返回值需要转换的类型)
JSONObject jsonObject = restTemplate.postForObject(url, body, JSONObject.class);
System.out.println(jsonObject);
}使用postForObject
@Test
public void postRequestUsingPostForObject() {
String url = "https://jsonplaceholder.typicode.com/posts";
// 创建请求体
String requestBody = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
// 使用 postForObject 发送 POST 请求并返回响应体
String response = restTemplate.postForObject(url, requestBody, String.class);
// 打印响应
System.out.println(response);
}使用exchange
@Test
public void postRequestUsingExchange() {
String url = "https://jsonplaceholder.typicode.com/posts";
// 构建请求体
String requestBody = "{\"title\":\"foo\",\"body\":\"bar\",\"userId\":1}";
// 创建HttpHeaders并设置Content-Type
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 创建HttpEntity,将请求体和请求头封装在一起
HttpEntity<String> entity = new HttpEntity<>(requestBody, headers);
// 使用 exchange 发送 POST 请求
ResponseEntity<String> response = restTemplate.exchange(
url,
HttpMethod.POST, // 请求方法为 POST
entity, // 请求体和头
String.class // 响应类型
);
// 打印响应
System.out.println(response.getBody());
}在请求之行前回之行的操作
package local.ateng.java.rest.interceptor;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class MyRequestInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution) throws IOException {
// 在请求之前执行的操作,比如添加请求头、日志记录等
System.out.println("请求 URL: " + request.getURI());
System.out.println("请求方法: " + request.getMethod());
// 继续执行请求
return execution.execute(request, body);
}
}package local.ateng.java.rest.config;
import local.ateng.java.rest.interceptor.MyRequestInterceptor;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.web.client.RestTemplate;
import java.util.ArrayList;
import java.util.List;
/**
* RestTemplate配置文件
*
* @author 孔余
* @email 2385569970@qq.com
* @since 2025-02-02
*/
@Configuration
@RequiredArgsConstructor
public class RestTemplateConfig {
private final MyRequestInterceptor myRequestInterceptor;
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
@Bean
public RestTemplate myRestTemplate() {
// 创建 RestTemplate
RestTemplate restTemplate = new RestTemplate();
// 创建并注册拦截器
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(myRequestInterceptor);
restTemplate.setInterceptors(interceptors);
return restTemplate;
}
}@SpringBootTest
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
class HTTPRestTemplateJdk8Tests {
private final RestTemplate restTemplate;
private final RestTemplate myRestTemplate;
@Test
void getForObjectOther() {
//getForObject方法,获取响应体,将其转换为第二个参数指定的类型
JSONObject jsonObject = myRestTemplate.getForObject("https://www.wanandroid.com/article/list/0/json", JSONObject.class);
String string = JSON.toJSONString(jsonObject, JSONWriter.Feature.PrettyFormat);
System.out.println(string);
}
}输出:
请求 URL: https://www.wanandroid.com/article/list/0/json
请求方法: GET
{
"data":{
...
package local.ateng.java.rest.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import java.io.IOException;
/**
* RestTemplate 拦截器:用于添加统一的认证 Token(如 JWT)到请求头中。
* <p>
* 注意:此实现为模拟 Token 获取逻辑,实际生产中应接入真实的缓存(如 Redis)或配置中心。
*
* @author 孔余
* @since 2025-07-30
*/
@Component
@Slf4j
public class AuthInterceptor implements ClientHttpRequestInterceptor {
/**
* 拦截请求,添加 Authorization 头。
*
* @param request 原始请求
* @param body 请求体字节数组
* @param execution 请求执行器(用于继续调用链)
* @return 响应结果
* @throws IOException IO 异常
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
// 获取 Token(可替换为从 Redis、配置中心或上下文中获取)
String token = getToken();
// 日志记录请求 URI 和添加 token 的行为(仅开发调试阶段启用)
log.debug("Adding Authorization token to request: {}", request.getURI());
// 若 token 为空,可选择记录日志或抛出异常(视业务场景而定)
if (token == null || token.isEmpty()) {
log.warn("Authorization token is missing.");
// 你也可以选择抛出自定义异常
// throw new IllegalStateException("Missing authorization token");
}
// 设置请求头
HttpHeaders headers = request.getHeaders();
headers.set(HttpHeaders.AUTHORIZATION, "Bearer " + token);
// 执行请求
return execution.execute(request, body);
}
/**
* 获取 Token 的方法(可扩展为从缓存或配置服务获取)
*
* @return JWT Token 字符串
*/
private String getToken() {
// 模拟:返回一个硬编码 Token(请替换为实际获取逻辑)
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...";
}
}package local.ateng.java.rest.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* RestTemplate 拦截器:记录请求和响应的详细日志
*
* @author 孔余
* @since 2025-07-30
*/
@Component
@Slf4j
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
/**
* 拦截请求并打印日志
*
* @param request 当前请求
* @param body 请求体
* @param execution 拦截器执行器(用于传递调用链)
* @return 响应结果
* @throws IOException IO 异常
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
long startTime = System.currentTimeMillis();
log.info("====== 请求开始 ======");
log.info("请求地址: {}", request.getURI());
log.info("请求方式: {}", request.getMethod());
log.info("请求头: {}", request.getHeaders());
// 记录请求体,仅限于有请求体的情况(如 POST、PUT)
if (body != null && body.length > 0) {
log.info("请求体: {}", new String(body, StandardCharsets.UTF_8));
} else {
log.info("请求体: 无");
}
ClientHttpResponse response;
try {
// 执行请求
response = execution.execute(request, body);
} catch (IOException e) {
log.error("请求执行异常: {}", e.getMessage(), e);
throw e;
}
// 包装响应体,避免响应流只能读取一次的问题
ClientHttpResponse wrappedResponse = new BufferingClientHttpResponseWrapper(response);
String responseBody = StreamUtils.copyToString(wrappedResponse.getBody(), StandardCharsets.UTF_8);
log.info("响应状态: {}", wrappedResponse.getStatusCode());
log.info("响应头: {}", wrappedResponse.getHeaders());
log.info("响应体: {}", responseBody);
log.info("耗时: {} ms", System.currentTimeMillis() - startTime);
log.info("====== 请求结束 ======");
return wrappedResponse;
}
/**
* 响应包装类,用于缓存响应体以便多次读取
*/
private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response;
private byte[] body;
public BufferingClientHttpResponseWrapper(ClientHttpResponse response) throws IOException {
this.response = response;
this.body = StreamUtils.copyToByteArray(response.getBody());
}
@Override
public org.springframework.http.HttpStatus getStatusCode() throws IOException {
return response.getStatusCode();
}
@Override
public int getRawStatusCode() throws IOException {
return response.getRawStatusCode();
}
@Override
public String getStatusText() throws IOException {
return response.getStatusText();
}
@Override
public void close() {
response.close();
}
@Override
public org.springframework.http.HttpHeaders getHeaders() {
return response.getHeaders();
}
@Override
public java.io.InputStream getBody() {
return new ByteArrayInputStream(body);
}
}
}package local.ateng.java.rest.interceptor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpStatusCodeException;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
/**
* RestTemplate 拦截器:用于在请求失败时自动进行重试,并支持指数退避策略。
*
* <p>支持以下两类失败情况自动重试:
* <ul>
* <li>网络异常(如连接超时、读取超时等)</li>
* <li>服务端错误(状态码 5xx)</li>
* </ul>
*
* <p>支持通过静态方法 {@link #setMaxRetries(int)} 动态设置最大重试次数,具备线程安全性。
* 默认采用指数退避策略,避免高并发下请求雪崩。
*
* @author 孔余
* @since 2025-07-30
*/
@Component
@Slf4j
public class RetryInterceptor implements ClientHttpRequestInterceptor {
/**
* 最大重试次数(支持线程安全修改)
* 默认为 3 次
*/
private static final AtomicInteger MAX_RETRIES = new AtomicInteger(3);
/**
* 初始重试等待时间(单位:毫秒),每次重试会指数增长
*/
private static final long INITIAL_INTERVAL_MS = 300;
/**
* 最大重试等待时间(单位:毫秒),用于限制指数退避的上限
*/
private static final long MAX_INTERVAL_MS = 5000;
/**
* 设置最大重试次数(必须大于 0)
*
* @param retries 新的最大重试次数
*/
public static void setMaxRetries(int retries) {
if (retries > 0) {
MAX_RETRIES.set(retries);
} else {
log.warn("设置的最大重试次数无效:{}", retries);
}
}
/**
* 拦截请求并执行重试逻辑
*
* @param request 当前请求对象
* @param body 请求体内容字节数组
* @param execution 请求执行器,用于继续调用链
* @return 请求响应
* @throws IOException 当超过最大重试次数后仍然失败,则抛出异常
*/
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
int attempt = 0;
while (true) {
try {
// 正常执行请求
return execution.execute(request, body);
} catch (HttpStatusCodeException e) {
// 处理服务端错误(5xx)重试
if (shouldRetry(e.getStatusCode().value()) && attempt < MAX_RETRIES.get()) {
attempt++;
long waitTime = calculateBackoffTime(attempt);
log.warn("请求失败(状态码:{}),第 {} 次重试:{},等待 {} 毫秒",
e.getStatusCode().value(), attempt, request.getURI(), waitTime);
sleep(waitTime);
} else {
log.error("请求失败,状态码:{},不再重试:{}", e.getStatusCode(), request.getURI());
throw e;
}
} catch (IOException e) {
// 网络异常重试
if (attempt < MAX_RETRIES.get()) {
attempt++;
long waitTime = calculateBackoffTime(attempt);
log.warn("网络异常,第 {} 次重试:{},等待 {} 毫秒,异常信息:{}",
attempt, request.getURI(), waitTime, e.getMessage());
sleep(waitTime);
} else {
log.error("网络异常,重试结束:{},异常信息:{}", request.getURI(), e.getMessage());
throw e;
}
}
}
}
/**
* 判断当前状态码是否应当进行重试
*
* @param statusCode HTTP 状态码
* @return 是否应当重试
*/
private boolean shouldRetry(int statusCode) {
// 默认只重试服务端错误(5xx)
return statusCode >= 500 && statusCode < 600;
}
/**
* 根据当前重试次数计算指数退避时间,并限制最大等待时间
*
* @param attempt 当前重试次数(从 1 开始)
* @return 等待时间(单位:毫秒)
*/
private long calculateBackoffTime(int attempt) {
long waitTime = (long) (INITIAL_INTERVAL_MS * Math.pow(2, attempt - 1));
return Math.min(waitTime, MAX_INTERVAL_MS);
}
/**
* 安全执行线程等待,如果被中断则恢复线程中断状态
*
* @param millis 等待时间(单位:毫秒)
*/
private void sleep(long millis) {
try {
Thread.sleep(millis);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
}
<!-- Apache HttpComponents客户端 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>/**
* 构建支持连接池的 RestTemplate Bean。
* 设置连接超时、读取超时等参数,适合生产环境使用。
*
* @return 配置后的 RestTemplate 实例
*/
@Bean
public RestTemplate restTemplate() {
// 创建底层 HTTP 请求工厂
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
// 设置连接超时时间(单位:毫秒),连接建立时限
factory.setConnectTimeout(5000);
// 设置读取超时时间(单位:毫秒),服务器响应时限
factory.setReadTimeout(10000);
// 设置从连接池获取连接的超时时间(单位:毫秒)
factory.setConnectionRequestTimeout(3000);
// 创建连接池管理器
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
// 设置最大连接数(整个连接池)
connManager.setMaxTotal(100);
// 设置每个路由(目标主机)上的最大连接数
connManager.setDefaultMaxPerRoute(20);
// 构建 HttpClient 并注入连接池管理器
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connManager)
.build();
// 将 HttpClient 设置到请求工厂中
factory.setHttpClient(httpClient);
// 返回使用自定义请求工厂的 RestTemplate 实例
return new RestTemplate(factory);
} /**
* 创建一个忽略 HTTPS 证书校验的 HttpClient,仅用于测试环境。
*
* @return CloseableHttpClient
*/
public static CloseableHttpClient createIgnoreSSLHttpClient() {
try {
SSLContext sslContext = SSLContextBuilder.create()
.loadTrustMaterial((chain, authType) -> true) // 信任所有
.build();
SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(
sslContext, NoopHostnameVerifier.INSTANCE);
return HttpClients.custom()
.setSSLSocketFactory(socketFactory)
.build();
} catch (Exception e) {
throw new RuntimeException("创建忽略证书校验 HttpClient 失败", e);
}
}
@Bean
public RestTemplate unsafeRestTemplate() {
// 使用忽略证书校验的 HttpClient
CloseableHttpClient httpClient = createIgnoreSSLHttpClient();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setHttpClient(httpClient);
return new RestTemplate(factory);
}package local.ateng.java.rest.config;
import lombok.RequiredArgsConstructor;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate 配置类
*
* <p>该配置类主要用于统一管理 RestTemplate 的实例化与底层 HTTP 客户端配置,
* 包括连接池管理、超时时间设置等。通过将参数抽取为全局常量,避免魔法值,
* 提升可维护性和可扩展性。</p>
*
* <p>本配置基于 Spring Boot 2.7 环境,适用于需要高并发访问外部接口的场景。</p>
*
* @author 孔余
* @since 2025-09-10
*/
@Configuration
@RequiredArgsConstructor
public class RestTemplateConfig {
/**
* 连接建立超时时间(毫秒)
*/
private static final int CONNECT_TIMEOUT = 5000;
/**
* 读取数据超时时间(毫秒)
*/
private static final int READ_TIMEOUT = 10000;
/**
* 从连接池获取连接的超时时间(毫秒)
*/
private static final int CONNECTION_REQUEST_TIMEOUT = 3000;
/**
* 连接池最大连接数
*/
private static final int MAX_TOTAL_CONNECTIONS = 100;
/**
* 每个路由(目标主机)的最大连接数
*/
private static final int MAX_CONNECTIONS_PER_ROUTE = 20;
/**
* 构建并注册 RestTemplate Bean
*
* <p>该方法会配置底层的 {@link HttpComponentsClientHttpRequestFactory},
* 并使用 {@link PoolingHttpClientConnectionManager} 来管理连接池。
* 最终生成的 RestTemplate 实例可直接在业务中通过注入方式使用。</p>
*
* @return 配置完成的 RestTemplate 实例
*/
@Bean("atengRestTemplate")
public RestTemplate atengRestTemplate() {
// 创建 HTTP 请求工厂
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
factory.setConnectTimeout(CONNECT_TIMEOUT);
factory.setReadTimeout(READ_TIMEOUT);
factory.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT);
// 创建连接池管理器
PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager();
connManager.setMaxTotal(MAX_TOTAL_CONNECTIONS);
connManager.setDefaultMaxPerRoute(MAX_CONNECTIONS_PER_ROUTE);
// 构建 HttpClient 并注入连接池管理器
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionManager(connManager)
.build();
// 将 HttpClient 设置到请求工厂中
factory.setHttpClient(httpClient);
// 创建 RestTemplate
RestTemplate restTemplate = new RestTemplate(factory);
// 创建并注册拦截器
/*List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(authInterceptor);
interceptors.add(loggingInterceptor);
interceptors.add(retryInterceptor);
restTemplate.setInterceptors(interceptors);*/
// 返回配置完成的 RestTemplate
return restTemplate;
}
}package local.ateng.java.rest.utils;
import cn.hutool.extra.spring.SpringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.util.Map;
/**
* RestUtil 工具类
*
* <p>基于自定义 {@code myRestTemplate} 封装常用的 HTTP 接口调用方法,
* 旨在简化服务间 RESTful 接口的调用流程,提高开发效率和代码可读性。</p>
*
* <h2>核心功能:</h2>
* <ul>
* <li>快速调用常用 HTTP 方法:GET、POST、PUT、DELETE</li>
* <li>通用 {@code exchange} 方法,支持自定义 HTTP 方法、请求体和泛型返回值</li>
* <li>支持多种请求体格式:JSON、XML、表单(x-www-form-urlencoded)、文件上传(multipart/form-data)</li>
* <li>统一异常捕获与日志记录,便于排查接口调用问题</li>
* <li>支持动态 URL 参数和占位符替换</li>
* </ul>
*
* <h2>使用示例:</h2>
* <pre>
* // JSON POST 请求
* Map<String, Object> payload = new HashMap<>();
* payload.put("id", 123);
* payload.put("name", "张三");
* HttpEntity<Map<String, Object>> entity = RestUtil.jsonBodyWithHeaders(payload, null);
*
* ResponseEntity<String> response = RestUtil.exchange(
* "https://api.example.com/user",
* HttpMethod.POST,
* entity,
* String.class
* );
*
* // GET 请求示例
* ResponseEntity<User> resp = RestUtil.get("https://api.example.com/user/{id}", User.class, 123);
*
* // 文件上传示例
* File file = new File("D:/test.png");
* HttpEntity<MultiValueMap<String, Object>> fileEntity =
* RestUtil.multipartBodyWithHeaders(file, "file", null);
* ResponseEntity<String> uploadResp = RestUtil.exchange(
* "https://api.example.com/upload",
* HttpMethod.POST,
* fileEntity,
* String.class
* );
* </pre>
*
* <h2>适用场景:</h2>
* <ul>
* <li>微服务间 RESTful 接口调用</li>
* <li>第三方 API 调用(JSON / XML / Form / File)</li>
* <li>需要统一接口调用日志和异常处理</li>
* </ul>
*
* <h2>注意事项:</h2>
* <ul>
* <li>本工具类依赖于注入的 {@code myRestTemplate} Bean</li>
* <li>POJO 请求体需保证 RestTemplate 配置了对应的 HttpMessageConverter(如 Jackson)</li>
* <li>文件上传需服务端支持 multipart/form-data</li>
* </ul>
*
* <p>通过 RestUtil,开发者可快速发起接口请求,无需重复构建 HttpEntity 或处理异常,提高了接口调用的一致性和可维护性。</p>
*
* @author 孔余
* @since 2025-09-09
*/
public class RestUtil {
private static final Logger log = LoggerFactory.getLogger(RestUtil.class);
/**
* Spring 容器中注入的自定义 RestTemplate
*/
private static final RestTemplate restTemplate = SpringUtil.getBean("atengRestTemplate", RestTemplate.class);
// ================= GET =================
/**
* 发送 GET 请求,返回响应体对象。
*
* <p>适用于直接获取目标类型的响应场景。
*
* <p>使用示例:
* <pre>
* String result = RestUtil.getForObject("http://example.com/api/{id}", String.class, 123);
* </pre>
*
* @param url 请求 URL,可包含占位符
* @param responseType 响应体类型
* @param uriVariables URL 中的占位符参数
* @param <T> 返回值泛型
* @return 响应体对象
* @throws RestClientException 请求失败时抛出
*/
public static <T> T getForObject(String url, Class<T> responseType, Object... uriVariables) {
try {
return restTemplate.getForObject(url, responseType, uriVariables);
} catch (RestClientException e) {
log.error("GET 请求失败: url={}, 参数={}", url, uriVariables, e);
throw e;
}
}
/**
* 发送 GET 请求,返回完整的 {@link ResponseEntity}。
*
* <p>适用于需要获取响应头、状态码等额外信息的场景。
*
* <p>使用示例:
* <pre>
* ResponseEntity<String> resp = RestUtil.getForEntity("http://example.com/api/{id}", String.class, 123);
* if (resp.getStatusCode().is2xxSuccessful()) {
* System.out.println(resp.getBody());
* }
* </pre>
*
* @param url 请求 URL,可包含占位符
* @param responseType 响应体类型
* @param uriVariables URL 中的占位符参数
* @param <T> 返回值泛型
* @return 包含响应状态码、头信息及响应体的对象
* @throws RestClientException 请求失败时抛出
*/
public static <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... uriVariables) {
try {
return restTemplate.getForEntity(url, responseType, uriVariables);
} catch (RestClientException e) {
log.error("GET 请求失败: url={}, 参数={}", url, uriVariables, e);
throw e;
}
}
// ================= POST =================
/**
* 发送 POST 请求,返回响应体对象。
*
* <p>适用于需要提交请求体并直接获取结果的场景。
*
* <p>使用示例:
* <pre>
* User user = new User("Tom", 18);
* String result = RestUtil.postForObject("http://example.com/save", user, String.class);
* </pre>
*
* @param url 请求 URL,可包含占位符
* @param request 请求体对象
* @param responseType 响应体类型
* @param uriVariables URL 中的占位符参数
* @param <T> 返回值泛型
* @return 响应体对象
* @throws RestClientException 请求失败时抛出
*/
public static <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables) {
try {
return restTemplate.postForObject(url, request, responseType, uriVariables);
} catch (RestClientException e) {
log.error("POST 请求失败: url={}, 请求体={}", url, request, e);
throw e;
}
}
/**
* 发送 POST 请求,返回完整的 {@link ResponseEntity}。
*
* <p>适用于需要获取响应头、状态码等额外信息的场景。
*
* <p>使用示例:
* <pre>
* User user = new User("Tom", 18);
* ResponseEntity<String> resp = RestUtil.postForEntity("http://example.com/save", user, String.class);
* if (resp.getStatusCode().is2xxSuccessful()) {
* System.out.println(resp.getBody());
* }
* </pre>
*
* @param url 请求 URL,可包含占位符
* @param request 请求体对象
* @param responseType 响应体类型
* @param uriVariables URL 中的占位符参数
* @param <T> 返回值泛型
* @return 包含响应状态码、头信息及响应体的对象
* @throws RestClientException 请求失败时抛出
*/
public static <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables) {
try {
return restTemplate.postForEntity(url, request, responseType, uriVariables);
} catch (RestClientException e) {
log.error("POST 请求失败: url={}, 请求体={}", url, request, e);
throw e;
}
}
// ================= PUT =================
/**
* 发送 PUT 请求,不返回响应体。
*
* <p>适用于更新资源的场景,通常 REST 接口返回 204 No Content。
*
* <p>使用示例:
* <pre>
* User user = new User("Tom", 20);
* RestUtil.put("http://example.com/update/{id}", user, 123);
* </pre>
*
* @param url 请求 URL,可包含占位符
* @param request 请求体对象
* @param uriVariables URL 中的占位符参数
* @throws RestClientException 请求失败时抛出
*/
public static void put(String url, Object request, Object... uriVariables) {
try {
restTemplate.put(url, request, uriVariables);
} catch (RestClientException e) {
log.error("PUT 请求失败: url={}, 请求体={}", url, request, e);
throw e;
}
}
// ================= DELETE =================
/**
* 发送 DELETE 请求。
*
* <p>适用于删除资源的场景。
*
* <p>使用示例:
* <pre>
* RestUtil.delete("http://example.com/delete/{id}", 123);
* </pre>
*
* @param url 请求 URL,可包含占位符
* @param uriVariables URL 中的占位符参数
* @throws RestClientException 请求失败时抛出
*/
public static void delete(String url, Object... uriVariables) {
try {
restTemplate.delete(url, uriVariables);
} catch (RestClientException e) {
log.error("DELETE 请求失败: url={}, 参数={}", url, uriVariables, e);
throw e;
}
}
// ================= Exchange =================
/**
* 通用请求方法,支持指定 HTTP 方法、请求体、响应类型。
*
* <p>适用于需要高度自定义请求头或复杂请求场景。
*
* <p>使用示例:
* <pre>
* HttpHeaders headers = new HttpHeaders();
* headers.setContentType(MediaType.APPLICATION_JSON);
* HttpEntity<User> entity = new HttpEntity<>(new User("Tom", 18), headers);
*
* ResponseEntity<String> resp = RestUtil.exchange(
* "http://example.com/save",
* HttpMethod.POST,
* entity,
* String.class
* );
* </pre>
*
* @param url 请求 URL
* @param method HTTP 方法(GET, POST, PUT, DELETE 等)
* @param requestEntity 封装了请求头和请求体的实体对象
* @param responseType 响应体类型
* @param uriVariables URL 中的占位符参数
* @param <T> 返回值泛型
* @return 包含响应状态码、头信息及响应体的对象
* @throws RestClientException 请求失败时抛出
*/
public static <T> ResponseEntity<T> exchange(
String url,
HttpMethod method,
HttpEntity<?> requestEntity,
Class<T> responseType,
Object... uriVariables) {
try {
return restTemplate.exchange(url, method, requestEntity, responseType, uriVariables);
} catch (RestClientException e) {
log.error("HTTP 请求失败: url={}, method={}, 请求体={}", url, method, requestEntity, e);
throw e;
}
}
/**
* 通用请求方法,支持复杂的泛型返回值(如 List<Map<String,Object>>)。
*
* <p>适用于需要反序列化为复杂集合或泛型对象的场景。
*
* <p>使用示例:
* <pre>
* HttpHeaders headers = new HttpHeaders();
* headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
* HttpEntity<Void> entity = new HttpEntity<>(headers);
*
* ResponseEntity<List<User>> resp = RestUtil.exchange(
* "http://example.com/users",
* HttpMethod.GET,
* entity,
* new ParameterizedTypeReference<List<User>>() {}
* );
* </pre>
*
* @param url 请求 URL
* @param method HTTP 方法(GET, POST, PUT, DELETE 等)
* @param requestEntity 封装了请求头和请求体的实体对象
* @param responseType 响应体类型的泛型引用
* @param <T> 返回值泛型
* @return 包含响应状态码、头信息及响应体的对象
* @throws RestClientException 请求失败时抛出
*/
public static <T> ResponseEntity<T> exchange(
String url,
HttpMethod method,
HttpEntity<?> requestEntity,
ParameterizedTypeReference<T> responseType) {
try {
return restTemplate.exchange(url, method, requestEntity, responseType);
} catch (RestClientException e) {
log.error("HTTP 请求失败: url={}, method={}, 请求体={}", url, method, requestEntity, e);
throw e;
}
}
/**
* 构建仅包含请求头的 {@link HttpEntity}。
*
* <p>使用示例:
* <pre>
* HttpEntity<Void> entity = RestEntityBuilder.headersOnly(Collections.singletonMap("Authorization", "Bearer xxx"));
* ResponseEntity<String> resp = RestUtil.exchange(url, HttpMethod.GET, entity, String.class);
* </pre>
*
* @param headersMap 请求头键值对
* @return HttpEntity 实例
*/
public static HttpEntity<Void> headersOnly(Map<String, String> headersMap) {
HttpHeaders headers = new HttpHeaders();
if (headersMap != null) {
headersMap.forEach(headers::set);
}
return new HttpEntity<>(headers);
}
/**
* 构建包含 JSON 请求体的 {@link HttpEntity}。
*
* <p>Content-Type 自动设置为 {@code application/json}。
*
* <p>使用示例:
* <pre>
* User user = new User("Tom", 18);
* HttpEntity<User> entity = RestEntityBuilder.jsonBody(user);
* ResponseEntity<String> resp = RestUtil.exchange(url, HttpMethod.POST, entity, String.class);
* </pre>
*
* @param body 请求体对象
* @param <T> 请求体类型
* @return HttpEntity 实例
*/
public static <T> HttpEntity<T> jsonBody(T body) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return new HttpEntity<>(body, headers);
}
/**
* 构建包含 XML 请求体的 {@link HttpEntity}。
*
* <p>Content-Type 自动设置为 {@code application/xml}。
* <p>适用于调用需要 XML 格式请求体的接口,例如部分 SOAP/老系统接口。
*
* <p>使用示例:
* <pre>
* String xml = "<user><name>Tom</name><age>18</age></user>";
* HttpEntity<String> entity = RestEntityBuilder.xmlBody(xml);
* ResponseEntity<String> resp = RestUtil.exchange(url, HttpMethod.POST, entity, String.class);
* </pre>
*
* @param xmlBody XML 格式的字符串
* @return HttpEntity 实例
*/
public static HttpEntity<String> xmlBody(String xmlBody) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_XML);
return new HttpEntity<>(xmlBody, headers);
}
/**
* 通用构建方法:根据传入的请求体、请求头 map 和 Content-Type 构建 {@link HttpEntity}。
*
* <p>使用示例(POJO -> JSON):
* <pre>
* User user = new User("Tom", 18);
* HttpEntity<User> entity = RestEntityBuilder.bodyWithHeaders(user,
* Collections.singletonMap("Authorization", "Bearer x"),
* MediaType.APPLICATION_JSON);
* ResponseEntity<String> resp = RestUtil.exchange(url, HttpMethod.POST, entity, String.class);
* </pre>
*
* <p>使用示例(已经是 JSON 字符串):
* <pre>
* String json = "{\"id\":123,\"name\":\"张三\"}";
* HttpEntity<String> entity = RestEntityBuilder.bodyWithHeaders(json,
* Collections.singletonMap("X-Custom","v"),
* MediaType.APPLICATION_JSON);
* </pre>
*
* @param body 请求体,可以是 {@code String}(已序列化)或 POJO(由 RestTemplate 的 converter 序列化)
* @param headersMap 请求头键值对(可为 null)
* @param contentType Content-Type(可为 null,null 时不会设置 Content-Type)
* @param <T> 请求体类型
* @return 构建好的 {@link HttpEntity}
*/
public static <T> HttpEntity<T> bodyWithHeaders(T body, Map<String, String> headersMap, MediaType contentType) {
HttpHeaders headers = new HttpHeaders();
if (headersMap != null && !headersMap.isEmpty()) {
headersMap.forEach(headers::set);
}
if (contentType != null) {
headers.setContentType(contentType);
}
return new HttpEntity<>(body, headers);
}
/**
* 快捷:构建 JSON 请求体的 {@link HttpEntity}(委托给 {@link #bodyWithHeaders})。
*
* <p>说明:
* <ul>
* <li>当 body 为 POJO 时,RestTemplate 会使用 Jackson 等 converter 自动序列化为 JSON(前提是相关 converter 已配置)。</li>
* <li>当 body 为 String 时,方法会把该字符串原样作为请求体发送(不会再做序列化)。</li>
* </ul>
* </p>
*
* <p>使用示例(POJO):</p>
* <pre>
* User user = new User("Tom", 18);
* HttpEntity<User> entity = RestEntityBuilder.jsonBodyWithHeaders(user,
* Collections.singletonMap("Authorization", "Bearer xxx"));
* ResponseEntity<String> resp = RestUtil.exchange(url, HttpMethod.POST, entity, String.class);
* </pre>
*
* @param body 请求体(POJO 或已序列化的 JSON 字符串)
* @param headersMap 请求头(可为 null)
* @param <T> 请求体类型
* @return HttpEntity
*/
public static <T> HttpEntity<T> jsonBodyWithHeaders(T body, Map<String, String> headersMap) {
return bodyWithHeaders(body, headersMap, MediaType.APPLICATION_JSON);
}
/**
* 快捷:构建 XML 请求体的 {@link HttpEntity}(委托给 {@link #bodyWithHeaders})。
*
* <p>通常场景:向需要 application/xml 的老系统或 SOAP-like 接口发送 XML 字符串。</p>
*
* <p>使用示例(XML 字符串):</p>
* <pre>
* String xml = "<user><name>Tom</name><age>18</age></user>";
* HttpEntity<String> entity = RestEntityBuilder.xmlBodyWithHeaders(xml,
* Collections.singletonMap("Authorization", "Bearer xxx"));
* ResponseEntity<String> resp = RestUtil.exchange(url, HttpMethod.POST, entity, String.class);
* </pre>
*
* @param xmlBody XML 字符串(建议传 String)
* @param headersMap 请求头(可为 null)
* @return HttpEntity<String>
*/
public static HttpEntity<String> xmlBodyWithHeaders(String xmlBody, Map<String, String> headersMap) {
return bodyWithHeaders(xmlBody, headersMap, MediaType.APPLICATION_XML);
}
// ================= 表单(application/x-www-form-urlencoded) ================= //
/**
* 构建 application/x-www-form-urlencoded 的表单请求体。
*
* <p>使用示例:</p>
* <pre>
* Map<String, String> formData = new HashMap<>();
* formData.put("username", "tom");
* formData.put("password", "123456");
*
* HttpEntity<MultiValueMap<String, String>> entity =
* RestEntityBuilder.formBodyWithHeaders(formData, null);
* </pre>
*
* @param formData 表单数据(键值对)
* @param headersMap 请求头(可为 null)
* @return HttpEntity
*/
public static HttpEntity<MultiValueMap<String, String>> formBodyWithHeaders(Map<String, String> formData,
Map<String, String> headersMap) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
if (formData != null) {
formData.forEach(body::add);
}
return bodyWithHeaders(body, headersMap, MediaType.APPLICATION_FORM_URLENCODED);
}
// ================= 文件上传(multipart/form-data) ================= //
/**
* 构建 multipart/form-data 的文件上传请求体。
*
* <p>使用示例:</p>
* <pre>
* File file = new File("D:/test.png");
* HttpEntity<MultiValueMap<String, Object>> entity =
* RestEntityBuilder.multipartBodyWithHeaders(file, "file", null);
* </pre>
*
* @param file 文件对象
* @param fieldName 表单字段名,例如 "file"
* @param headersMap 请求头(可为 null)
* @return HttpEntity
*/
public static HttpEntity<MultiValueMap<String, Object>> multipartBodyWithHeaders(File file,
String fieldName,
Map<String, String> headersMap) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add(fieldName, file);
return bodyWithHeaders(body, headersMap, MediaType.MULTIPART_FORM_DATA);
}
/**
* 构建 multipart/form-data 的多文件上传请求体。
*
* @param files 多个文件(字段名相同)
* @param fieldName 表单字段名
* @param headersMap 请求头(可为 null)
* @return HttpEntity
*/
public static HttpEntity<MultiValueMap<String, Object>> multipartBodyWithHeaders(File[] files,
String fieldName,
Map<String, String> headersMap) {
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
if (files != null) {
for (File file : files) {
body.add(fieldName, file);
}
}
return bodyWithHeaders(body, headersMap, MediaType.MULTIPART_FORM_DATA);
}
}