SpringBoot应用中HttpClient请求出现ConnectException问题求助
在SpringBoot应用的StartListener的onApplicationEvent方法中执行以下HTTP请求代码时,每次都会抛出java.net.ConnectException:
try { System.out.println("StartListener.onApplicationEvent()"); HttpRequest request = HttpRequest.newBuilder().GET() .uri(URI.create( "https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-A-B-C-Corse.ics")) .build(); HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } catch (IOException | InterruptedException e) { e.printStackTrace(); }
异常栈信息如下:
java.net.ConnectException at java.net.http/jdk.internal.net.http.HttpClientImpl.send(HttpClientImpl.java:573) at java.net.http/jdk.internal.net.http.HttpClientFacade.send(HttpClientFacade.java:123) at fr.utensilia.covoit.back.config.StartListener.onApplicationEvent(StartListener.java:42) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.context.event.ApplicationListenerMethodAdapter.doInvoke(ApplicationListenerMethodAdapter.java:344) at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:229) at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:166) at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:176) at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:169) at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:143) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:421) at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:378) at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:938) at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) at fr.utensilia.covoit.CovoitApplication.main(CovoitApplication.java:10) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.base/java.lang.reflect.Method.invoke(Method.java:568) at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) Caused by: java.net.ConnectException at java.net.http/jdk.internal.net.http.common.Utils.toConnectException(Utils.java:1047) at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:198) at java.net.http/jdk.internal.net.http.PlainHttpConnection.checkRetryConnect(PlainHttpConnection.java:230) at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$1(PlainHttpConnection.java:206) at java.base/java.util.concurrent.CompletableFuture.uniHandle(CompletableFuture.java:934) at java.base/java.util.concurrent.CompletableFuture$UniHandle.tryFire(CompletableFuture.java:911) at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:510) at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1773) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) at java.base/java.lang.Thread.run(Thread.java:833) Caused by: java.nio.channels.ClosedChannelException at java.base/sun.nio.ch.SocketChannelImpl.ensureOpen(SocketChannelImpl.java:195) at java.base/sun.nio.ch.SocketChannelImpl.beginConnect(SocketChannelImpl.java:760) at java.base/sun.nio.ch.SocketChannelImpl.connect(SocketChannelImpl.java:848) at java.net.http/jdk.internal.net.http.PlainHttpConnection.lambda$connectAsync$0(PlainHttpConnection.java:183) at java.base/java.security.AccessController.doPrivileged(AccessController.java:569) at java.net.http/jdk.internal.net.http.PlainHttpConnection.connectAsync(PlainHttpConnection.java:185) ... 9 more
但将相同代码放到普通Java类中执行时却能正常运行:
import java.io.IOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; public class Test { public static void main(String[] args) { try { System.out.println("StartListener.onApplicationEvent()"); HttpRequest request = HttpRequest.newBuilder().GET() .uri(URI.create( "https://fr.ftp.opendatasoft.com/openscol/fr-en-calendrier-scolaire/Zone-A-B-C-Corse.ics")) .build(); HttpClient httpClient = HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); System.out.println(response.body()); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } }
同时浏览器也能正常打开该URL,以下是排查解决的思路:
检查代理配置差异:SpringBoot可能会加载系统级或配置文件中的代理设置,而普通Java程序默认不会读取这些配置。可以在代码中显式禁用代理测试:
HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .proxy(ProxySelector.of(new InetSocketAddress("localhost", 0))) // 禁用代理 .build();也可以打印系统属性
System.getProperty("https.proxyHost")、System.getProperty("https.proxyPort")确认是否有代理被自动设置。切换事件触发时机:当前请求是在容器刷新事件中执行,此时部分网络初始化逻辑可能未完成。可以将代码迁移到
ApplicationReadyEvent事件中执行,该事件在Spring容器完全启动、所有Bean初始化完成后触发。排查SpringDevTools影响:异常栈中出现了
RestartLauncher,说明启用了SpringDevTools。DevTools会修改类加载器和线程环境,可能影响网络请求。可以临时关闭DevTools(移除spring-boot-devtools依赖)后重新测试。切换HTTP版本:目标服务器可能对HTTP/2支持存在兼容性问题,尝试将HttpClient的版本改为HTTP/1.1:
HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_1_1) .build();添加超时与日志调试:为HttpClient添加超时配置,并开启网络日志,查看详细的交互过程:
HttpClient httpClient = HttpClient.newBuilder() .version(HttpClient.Version.HTTP_2) .connectTimeout(Duration.ofSeconds(10)) .build();在
application.properties中添加日志配置:logging.level.java.net.http=DEBUG logging.level.sun.net.www.protocol.https=DEBUG对比JVM参数差异:检查SpringBoot启动时的JVM参数和普通Java类启动时的参数,查看是否存在网络相关的限制(如
java.net.preferIPv6Addresses等),尝试统一参数后测试。
内容的提问来源于stack exchange,提问作者Josselin Beaud'huy

