diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java index bada702b233..db838d6ad06 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorClientHttpConnector.java @@ -46,6 +46,7 @@ import org.springframework.util.Assert; * @author Brian Clozel * @author Rossen Stoyanchev * @author Sebastien Deleuze + * @author Juergen Hoeller * @since 5.0 * @see reactor.netty.http.client.HttpClient */ @@ -72,6 +73,8 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif @Nullable private volatile HttpClient httpClient; + private boolean lazyStart = false; + private final Object lifecycleMonitor = new Object(); @@ -121,6 +124,9 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif if (resourceFactory.isRunning()) { this.httpClient = createHttpClient(resourceFactory, mapper); } + else { + this.lazyStart = true; + } } private static HttpClient createHttpClient(ReactorResourceFactory factory, Function mapper) { @@ -136,7 +142,21 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif HttpClient httpClient = this.httpClient; if (httpClient == null) { Assert.state(this.resourceFactory != null && this.mapper != null, "Illegal configuration"); - httpClient = createHttpClient(this.resourceFactory, this.mapper); + if (this.resourceFactory.isRunning()) { + // Retain HttpClient instance if resource factory has been started in the meantime, + // considering this connector instance as lazily started as well. + synchronized (this.lifecycleMonitor) { + httpClient = this.httpClient; + if (httpClient == null && this.lazyStart) { + httpClient = createHttpClient(this.resourceFactory, this.mapper); + this.httpClient = httpClient; + this.lazyStart = false; + } + } + } + if (httpClient == null) { + httpClient = createHttpClient(this.resourceFactory, this.mapper); + } } HttpClient.RequestSender requestSender = httpClient @@ -185,6 +205,7 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif synchronized (this.lifecycleMonitor) { if (this.httpClient == null) { this.httpClient = createHttpClient(this.resourceFactory, this.mapper); + this.lazyStart = false; } } } @@ -199,6 +220,7 @@ public class ReactorClientHttpConnector implements ClientHttpConnector, SmartLif if (this.resourceFactory != null && this.mapper != null) { synchronized (this.lifecycleMonitor) { this.httpClient = null; + this.lazyStart = false; } } } diff --git a/spring-web/src/test/java/org/springframework/http/client/reactive/ReactorClientHttpConnectorTests.java b/spring-web/src/test/java/org/springframework/http/client/reactive/ReactorClientHttpConnectorTests.java index bd1579b129c..4fbd278fad6 100644 --- a/spring-web/src/test/java/org/springframework/http/client/reactive/ReactorClientHttpConnectorTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/reactive/ReactorClientHttpConnectorTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2024 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,21 @@ package org.springframework.http.client.reactive; +import java.net.URI; import java.util.function.Function; import org.junit.jupiter.api.Test; +import reactor.core.publisher.Mono; import reactor.netty.http.client.HttpClient; +import org.springframework.http.HttpMethod; import org.springframework.http.client.ReactorResourceFactory; import static org.assertj.core.api.Assertions.assertThat; /** * @author Sebastien Deleuze + * @author Juergen Hoeller * @since 6.1 */ class ReactorClientHttpConnectorTests { @@ -41,6 +45,8 @@ class ReactorClientHttpConnectorTests { assertThat(connector.isRunning()).isTrue(); connector.start(); assertThat(connector.isRunning()).isTrue(); + connector.stop(); + assertThat(connector.isRunning()).isTrue(); } @Test @@ -54,6 +60,8 @@ class ReactorClientHttpConnectorTests { assertThat(connector.isRunning()).isTrue(); connector.start(); assertThat(connector.isRunning()).isTrue(); + connector.stop(); + assertThat(connector.isRunning()).isTrue(); } @Test @@ -69,6 +77,8 @@ class ReactorClientHttpConnectorTests { assertThat(connector.isRunning()).isFalse(); connector.start(); assertThat(connector.isRunning()).isTrue(); + connector.stop(); + assertThat(connector.isRunning()).isFalse(); } @Test @@ -84,6 +94,27 @@ class ReactorClientHttpConnectorTests { assertThat(connector.isRunning()).isFalse(); connector.start(); assertThat(connector.isRunning()).isTrue(); + connector.stop(); + assertThat(connector.isRunning()).isFalse(); + } + + @Test + void lazyStartWithExternalResourceFactory() throws Exception { + ReactorResourceFactory resourceFactory = new ReactorResourceFactory(); + Function mapper = Function.identity(); + ReactorClientHttpConnector connector = new ReactorClientHttpConnector(resourceFactory, mapper); + assertThat(connector.isRunning()).isFalse(); + resourceFactory.start(); + connector.connect(HttpMethod.GET, new URI(""), request -> Mono.empty()); + assertThat(connector.isRunning()).isTrue(); + connector.stop(); + assertThat(connector.isRunning()).isFalse(); + connector.connect(HttpMethod.GET, new URI(""), request -> Mono.empty()); + assertThat(connector.isRunning()).isFalse(); + connector.start(); + assertThat(connector.isRunning()).isTrue(); + connector.stop(); + assertThat(connector.isRunning()).isFalse(); } }