Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit dc46d9d

Browse files
authored
🎨 #3640 【微信支付】使用HttpClient发送http请求时调整为使用连接池的形式
1 parent bc6fb7b commit dc46d9d

File tree

5 files changed

+388
-17
lines changed

5 files changed

+388
-17
lines changed

‎weixin-java-pay/CONNECTION_POOL.md‎

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
# HTTP连接池功能说明
2+
3+
## 概述
4+
5+
`WxPayServiceApacheHttpImpl` 现在支持HTTP连接池功能,可以显著提高高并发场景下的性能表现。
6+
7+
## 主要改进
8+
9+
1. **连接复用**: 不再为每个请求创建新的HttpClient实例,而是复用连接池中的连接
10+
2. **性能提升**: 减少连接建立和销毁的开销,提高吞吐量
11+
3. **资源优化**: 合理控制并发连接数,避免资源浪费
12+
4. **SSL支持**: 同时支持普通HTTP和SSL连接的连接池
13+
14+
## 配置说明
15+
16+
### 默认配置
17+
```java
18+
WxPayConfig config = new WxPayConfig();
19+
// 默认配置:
20+
// maxConnTotal = 20 (最大连接数)
21+
// maxConnPerRoute = 10 (每个路由最大连接数)
22+
```
23+
24+
### 自定义配置
25+
```java
26+
WxPayConfig config = new WxPayConfig();
27+
config.setMaxConnTotal(50); // 设置最大连接数
28+
config.setMaxConnPerRoute(20); // 设置每个路由最大连接数
29+
```
30+
31+
## 使用方式
32+
33+
连接池功能是自动启用的,无需额外配置:
34+
35+
```java
36+
// 1. 配置微信支付
37+
WxPayConfig config = new WxPayConfig();
38+
config.setAppId("your-app-id");
39+
config.setMchId("your-mch-id");
40+
config.setMchKey("your-mch-key");
41+
42+
// 2. 创建支付服务(连接池自动启用)
43+
WxPayServiceApacheHttpImpl payService = new WxPayServiceApacheHttpImpl();
44+
payService.setConfig(config);
45+
46+
// 3. 正常使用,所有HTTP请求都会使用连接池
47+
WxPayUnifiedOrderResult result = payService.unifiedOrder(request);
48+
```
49+
50+
## 向后兼容性
51+
52+
- 此功能完全向后兼容,现有代码无需修改
53+
- 如果不设置连接池参数,将使用默认配置
54+
- 支持原有的HttpClientBuilderCustomizer自定义功能
55+
56+
## 注意事项
57+
58+
1. 连接池中的HttpClient实例会被复用,不要手动关闭
59+
2. SSL连接和普通连接使用不同的连接池
60+
3. 连接池参数建议根据实际并发量调整
61+
4. 代理配置仍然正常工作
62+
63+
## 性能建议
64+
65+
- 对于高并发应用,建议适当增加`maxConnTotal``maxConnPerRoute`
66+
- 监控连接池使用情况,避免连接数不足导致的阻塞
67+
- 在容器环境中,注意连接池配置与容器资源限制的平衡

‎weixin-java-pay/src/main/java/com/github/binarywang/wxpay/config/WxPayConfig.java‎

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,16 @@
1414
import lombok.extern.slf4j.Slf4j;
1515
import org.apache.commons.lang3.RegExUtils;
1616
import org.apache.commons.lang3.StringUtils;
17+
import org.apache.http.HttpHost;
18+
import org.apache.http.auth.AuthScope;
19+
import org.apache.http.auth.UsernamePasswordCredentials;
20+
import org.apache.http.client.CredentialsProvider;
21+
import org.apache.http.impl.client.BasicCredentialsProvider;
1722
import org.apache.http.impl.client.CloseableHttpClient;
23+
import org.apache.http.impl.client.HttpClients;
24+
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
25+
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
26+
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
1827
import org.apache.http.ssl.SSLContexts;
1928

2029
import javax.net.ssl.SSLContext;
@@ -185,11 +194,32 @@ public class WxPayConfig {
185194

186195

187196
private CloseableHttpClient apiV3HttpClient;
197+
198+
/**
199+
* 用于普通支付接口的可复用HttpClient,使用连接池
200+
*/
201+
private CloseableHttpClient httpClient;
202+
203+
/**
204+
* 用于需要SSL证书的支付接口的可复用HttpClient,使用连接池
205+
*/
206+
private CloseableHttpClient sslHttpClient;
207+
188208
/**
189209
* 支持扩展httpClientBuilder
190210
*/
191211
private HttpClientBuilderCustomizer httpClientBuilderCustomizer;
192212
private HttpClientBuilderCustomizer apiV3HttpClientBuilderCustomizer;
213+
214+
/**
215+
* HTTP连接池最大连接数,默认20
216+
*/
217+
private int maxConnTotal = 20;
218+
219+
/**
220+
* HTTP连接池每个路由的最大连接数,默认10
221+
*/
222+
private int maxConnPerRoute = 10;
193223
/**
194224
* 私钥信息
195225
*/
@@ -498,4 +528,111 @@ private Object[] p12ToPem() {
498528
return null;
499529

500530
}
531+
532+
/**
533+
* 初始化使用连接池的HttpClient
534+
*
535+
* @return CloseableHttpClient
536+
* @throws WxPayException 初始化异常
537+
*/
538+
public CloseableHttpClient initHttpClient() throws WxPayException {
539+
if (this.httpClient != null) {
540+
return this.httpClient;
541+
}
542+
543+
// 创建连接池管理器
544+
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
545+
connectionManager.setMaxTotal(this.maxConnTotal);
546+
connectionManager.setDefaultMaxPerRoute(this.maxConnPerRoute);
547+
548+
// 创建HttpClient构建器
549+
org.apache.http.impl.client.HttpClientBuilder httpClientBuilder = HttpClients.custom()
550+
.setConnectionManager(connectionManager);
551+
552+
// 配置代理
553+
configureProxy(httpClientBuilder);
554+
555+
// 提供自定义httpClientBuilder的能力
556+
Optional.ofNullable(httpClientBuilderCustomizer).ifPresent(e -> {
557+
e.customize(httpClientBuilder);
558+
});
559+
560+
this.httpClient = httpClientBuilder.build();
561+
return this.httpClient;
562+
}
563+
564+
/**
565+
* 初始化使用连接池且支持SSL的HttpClient
566+
*
567+
* @return CloseableHttpClient
568+
* @throws WxPayException 初始化异常
569+
*/
570+
public CloseableHttpClient initSslHttpClient() throws WxPayException {
571+
if (this.sslHttpClient != null) {
572+
return this.sslHttpClient;
573+
}
574+
575+
// 初始化SSL上下文
576+
SSLContext sslContext = this.getSslContext();
577+
if (null == sslContext) {
578+
sslContext = this.initSSLContext();
579+
}
580+
581+
// 创建支持SSL的连接池管理器
582+
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
583+
connectionManager.setMaxTotal(this.maxConnTotal);
584+
connectionManager.setDefaultMaxPerRoute(this.maxConnPerRoute);
585+
586+
// 创建HttpClient构建器,配置SSL
587+
org.apache.http.impl.client.HttpClientBuilder httpClientBuilder = HttpClients.custom()
588+
.setConnectionManager(connectionManager)
589+
.setSSLSocketFactory(new SSLConnectionSocketFactory(sslContext, new DefaultHostnameVerifier()));
590+
591+
// 配置代理
592+
configureProxy(httpClientBuilder);
593+
594+
// 提供自定义httpClientBuilder的能力
595+
Optional.ofNullable(httpClientBuilderCustomizer).ifPresent(e -> {
596+
e.customize(httpClientBuilder);
597+
});
598+
599+
this.sslHttpClient = httpClientBuilder.build();
600+
return this.sslHttpClient;
601+
}
602+
603+
/**
604+
* 配置HTTP代理
605+
*/
606+
private void configureProxy(org.apache.http.impl.client.HttpClientBuilder httpClientBuilder) {
607+
if (StringUtils.isNotBlank(this.getHttpProxyHost()) && this.getHttpProxyPort() > 0) {
608+
if (StringUtils.isEmpty(this.getHttpProxyUsername())) {
609+
this.setHttpProxyUsername("whatever");
610+
}
611+
612+
// 使用代理服务器 需要用户认证的代理服务器
613+
CredentialsProvider provider = new BasicCredentialsProvider();
614+
provider.setCredentials(new AuthScope(this.getHttpProxyHost(), this.getHttpProxyPort()),
615+
new UsernamePasswordCredentials(this.getHttpProxyUsername(), this.getHttpProxyPassword()));
616+
httpClientBuilder.setDefaultCredentialsProvider(provider)
617+
.setProxy(new HttpHost(this.getHttpProxyHost(), this.getHttpProxyPort()));
618+
}
619+
}
620+
621+
/**
622+
* 获取用于普通支付接口的HttpClient
623+
*
624+
* @return CloseableHttpClient
625+
*/
626+
public CloseableHttpClient getHttpClient() {
627+
return httpClient;
628+
}
629+
630+
/**
631+
* 获取用于SSL支付接口的HttpClient
632+
*
633+
* @return CloseableHttpClient
634+
*/
635+
public CloseableHttpClient getSslHttpClient() {
636+
return sslHttpClient;
637+
}
501638
}

‎weixin-java-pay/src/main/java/com/github/binarywang/wxpay/service/impl/WxPayServiceApacheHttpImpl.java‎

Lines changed: 37 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -52,15 +52,15 @@ public class WxPayServiceApacheHttpImpl extends BaseWxPayServiceImpl {
5252
@Override
5353
public byte[] postForBytes(String url, String requestStr, boolean useKey) throws WxPayException {
5454
try {
55-
HttpClientBuilder httpClientBuilder = createHttpClientBuilder(useKey);
5655
HttpPost httpPost = this.createHttpPost(url, requestStr);
57-
try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
58-
final byte[] bytes = httpClient.execute(httpPost, ByteArrayResponseHandler.INSTANCE);
59-
final String responseData = Base64.getEncoder().encodeToString(bytes);
60-
this.logRequestAndResponse(url, requestStr, responseData);
61-
wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
62-
return bytes;
63-
}
56+
CloseableHttpClient httpClient = this.createHttpClient(useKey);
57+
58+
// 使用连接池的客户端,不需要手动关闭
59+
final byte[] bytes = httpClient.execute(httpPost, ByteArrayResponseHandler.INSTANCE);
60+
final String responseData = Base64.getEncoder().encodeToString(bytes);
61+
this.logRequestAndResponse(url, requestStr, responseData);
62+
wxApiData.set(new WxPayApiData(url, requestStr, responseData, null));
63+
return bytes;
6464
} catch (Exception e) {
6565
this.logError(url, requestStr, e);
6666
wxApiData.set(new WxPayApiData(url, requestStr, null, e.getMessage()));
@@ -71,17 +71,17 @@ public byte[] postForBytes(String url, String requestStr, boolean useKey) throws
7171
@Override
7272
public String post(String url, String requestStr, boolean useKey) throws WxPayException {
7373
try {
74-
HttpClientBuilder httpClientBuilder = this.createHttpClientBuilder(useKey);
7574
HttpPost httpPost = this.createHttpPost(url, requestStr);
76-
try (CloseableHttpClient httpClient = httpClientBuilder.build()) {
77-
try (CloseableHttpResponseresponse = httpClient.execute(httpPost)) {
78-
StringresponseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
79-
this.logRequestAndResponse(url, requestStr, responseString);
80-
if (this.getConfig().isIfSaveApiData()) {
81-
wxApiData.set(newWxPayApiData(url, requestStr, responseString, null));
82-
}
83-
returnresponseString;
75+
CloseableHttpClient httpClient = this.createHttpClient(useKey);
76+
77+
// 使用连接池的客户端,不需要手动关闭
78+
try (CloseableHttpResponseresponse = httpClient.execute(httpPost)) {
79+
StringresponseString = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
80+
this.logRequestAndResponse(url, requestStr, responseString);
81+
if (this.getConfig().isIfSaveApiData()) {
82+
wxApiData.set(newWxPayApiData(url, requestStr, responseString, null));
8483
}
84+
return responseString;
8585
} finally {
8686
httpPost.releaseConnection();
8787
}
@@ -281,6 +281,26 @@ private CloseableHttpClient createApiV3HttpClient() throws WxPayException {
281281
return apiV3HttpClient;
282282
}
283283

284+
CloseableHttpClient createHttpClient(boolean useKey) throws WxPayException {
285+
if (useKey) {
286+
// 使用SSL连接池客户端
287+
CloseableHttpClient sslHttpClient = this.getConfig().getSslHttpClient();
288+
if (null == sslHttpClient) {
289+
this.getConfig().initSslHttpClient();
290+
sslHttpClient = this.getConfig().getSslHttpClient();
291+
}
292+
return sslHttpClient;
293+
} else {
294+
// 使用普通连接池客户端
295+
CloseableHttpClient httpClient = this.getConfig().getHttpClient();
296+
if (null == httpClient) {
297+
this.getConfig().initHttpClient();
298+
httpClient = this.getConfig().getHttpClient();
299+
}
300+
return httpClient;
301+
}
302+
}
303+
284304
private static StringEntity createEntry(String requestStr) {
285305
return new StringEntity(requestStr, ContentType.create(APPLICATION_JSON, StandardCharsets.UTF_8));
286306
//return new StringEntity(new String(requestStr.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.github.binarywang.wxpay.service.impl;
2+
3+
import com.github.binarywang.wxpay.config.WxPayConfig;
4+
import org.apache.http.impl.client.CloseableHttpClient;
5+
import org.testng.Assert;
6+
import org.testng.annotations.Test;
7+
8+
/**
9+
* 演示连接池功能的示例测试
10+
*/
11+
public class ConnectionPoolUsageExampleTest {
12+
13+
@Test
14+
public void demonstrateConnectionPoolUsage() throws Exception {
15+
// 1. 创建配置并设置连接池参数
16+
WxPayConfig config = new WxPayConfig();
17+
config.setAppId("wx123456789");
18+
config.setMchId("1234567890");
19+
config.setMchKey("32位商户密钥32位商户密钥32位商户密钥");
20+
21+
// 设置连接池参数(可选,有默认值)
22+
config.setMaxConnTotal(50); // 最大连接数,默认20
23+
config.setMaxConnPerRoute(20); // 每个路由最大连接数,默认10
24+
25+
// 2. 初始化连接池
26+
CloseableHttpClient pooledClient = config.initHttpClient();
27+
Assert.assertNotNull(pooledClient);
28+
29+
// 3. 创建支付服务实例
30+
WxPayServiceApacheHttpImpl payService = new WxPayServiceApacheHttpImpl();
31+
payService.setConfig(config);
32+
33+
// 4. 现在所有的HTTP请求都会使用连接池
34+
// 对于非SSL请求,会复用同一个HttpClient实例
35+
CloseableHttpClient client1 = payService.createHttpClient(false);
36+
CloseableHttpClient client2 = payService.createHttpClient(false);
37+
Assert.assertSame(client1, client2, "非SSL请求应该复用同一个客户端实例");
38+
39+
// 对于SSL请求,也会复用同一个SSL HttpClient实例(需要配置证书后)
40+
System.out.println("连接池配置成功!");
41+
System.out.println("最大连接数:" + config.getMaxConnTotal());
42+
System.out.println("每路由最大连接数:" + config.getMaxConnPerRoute());
43+
}
44+
45+
@Test
46+
public void demonstrateDefaultConfiguration() throws Exception {
47+
// 使用默认配置的示例
48+
WxPayConfig config = new WxPayConfig();
49+
config.setAppId("wx123456789");
50+
config.setMchId("1234567890");
51+
config.setMchKey("32位商户密钥32位商户密钥32位商户密钥");
52+
53+
// 不设置连接池参数,使用默认值
54+
CloseableHttpClient client = config.initHttpClient();
55+
Assert.assertNotNull(client);
56+
57+
// 验证默认配置
58+
Assert.assertEquals(config.getMaxConnTotal(), 20, "默认最大连接数应该是20");
59+
Assert.assertEquals(config.getMaxConnPerRoute(), 10, "默认每路由最大连接数应该是10");
60+
}
61+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /