From c0bef2c693d7d6855ac37b1c52b5f67dd93c126e Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 6 Jun 2024 08:54:32 +0200 Subject: [PATCH 1/2] Lazily start resources on demand (if necessary outside of lifecycle) See gh-32945 --- .../http/client/ReactorResourceFactory.java | 38 +++++++++++++++---- .../client/ReactorResourceFactoryTests.java | 18 +++++++++ 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/ReactorResourceFactory.java b/spring-web/src/main/java/org/springframework/http/client/ReactorResourceFactory.java index faed920cf66..3d1a0982acb 100644 --- a/spring-web/src/main/java/org/springframework/http/client/ReactorResourceFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/ReactorResourceFactory.java @@ -61,12 +61,12 @@ public class ReactorResourceFactory private Supplier connectionProviderSupplier = () -> ConnectionProvider.create("webflux", 500); @Nullable - private ConnectionProvider connectionProvider; + private volatile ConnectionProvider connectionProvider; private Supplier loopResourcesSupplier = () -> LoopResources.create("webflux-http"); @Nullable - private LoopResources loopResources; + private volatile LoopResources loopResources; private boolean manageConnectionProvider = false; @@ -141,16 +141,22 @@ public class ReactorResourceFactory /** * Return the configured {@link ConnectionProvider}. + *

Lazily tries to start the resources on demand if not initialized yet. + * @see #start() */ public ConnectionProvider getConnectionProvider() { - Assert.state(this.connectionProvider != null, "ConnectionProvider not initialized yet"); - return this.connectionProvider; + if (this.connectionProvider == null) { + start(); + } + ConnectionProvider connectionProvider = this.connectionProvider; + Assert.state(connectionProvider != null, "ConnectionProvider not initialized"); + return connectionProvider; } /** * Use this when you don't want to participate in global resources and * you want to customize the creation of the managed {@code LoopResources}. - *

By default, {@code LoopResources.create("reactor-http")} is used. + *

By default, {@code LoopResources.create("webflux-http")} is used. *

Note that this option is ignored if {@code userGlobalResources=false} or * {@link #setLoopResources(LoopResources)} is set. * @param supplier the supplier to use @@ -170,10 +176,16 @@ public class ReactorResourceFactory /** * Return the configured {@link LoopResources}. + *

Lazily tries to start the resources on demand if not initialized yet. + * @see #start() */ public LoopResources getLoopResources() { - Assert.state(this.loopResources != null, "LoopResources not initialized yet"); - return this.loopResources; + if (this.loopResources == null) { + start(); + } + LoopResources loopResources = this.loopResources; + Assert.state(loopResources != null, "LoopResources not initialized"); + return loopResources; } /** @@ -220,6 +232,12 @@ public class ReactorResourceFactory } + /** + * Starts the resources if initialized outside an ApplicationContext. + * This is for backwards compatibility; the preferred way is to rely on + * the ApplicationContext's {@link SmartLifecycle lifecycle management}. + * @see #start() + */ @Override public void afterPropertiesSet() { if (this.applicationContext == null) { @@ -227,6 +245,12 @@ public class ReactorResourceFactory } } + /** + * Stops the resources if initialized outside an ApplicationContext. + * This is for backwards compatibility; the preferred way is to rely on + * the ApplicationContext's {@link SmartLifecycle lifecycle management}. + * @see #stop() + */ @Override public void destroy() { if (this.applicationContext == null) { diff --git a/spring-web/src/test/java/org/springframework/http/client/ReactorResourceFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/ReactorResourceFactoryTests.java index a08c8983a6b..86d65e64ef9 100644 --- a/spring-web/src/test/java/org/springframework/http/client/ReactorResourceFactoryTests.java +++ b/spring-web/src/test/java/org/springframework/http/client/ReactorResourceFactoryTests.java @@ -261,4 +261,22 @@ class ReactorResourceFactoryTests { assertThat(resourceFactory.isRunning()).isFalse(); } + @Test + void lazilyStartOnConnectionProviderAccess() { + assertThat(this.resourceFactory.isRunning()).isFalse(); + this.resourceFactory.getConnectionProvider(); + assertThat(this.resourceFactory.isRunning()).isTrue(); + this.resourceFactory.stop(); + assertThat(this.resourceFactory.isRunning()).isFalse(); + } + + @Test + void lazilyStartOnLoopResourcesAccess() { + assertThat(this.resourceFactory.isRunning()).isFalse(); + this.resourceFactory.getLoopResources(); + assertThat(this.resourceFactory.isRunning()).isTrue(); + this.resourceFactory.stop(); + assertThat(this.resourceFactory.isRunning()).isFalse(); + } + } From 61d045ce5293b0e2664bb957c2be6949ff55fdff Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Thu, 6 Jun 2024 08:54:37 +0200 Subject: [PATCH 2/2] Polishing --- .../HttpComponentsClientHttpRequestFactory.java | 11 ++++++----- .../client/reactive/ReactorNetty2ResourceFactory.java | 6 ++---- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java index 830165989d4..d5d73e8f891 100644 --- a/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactory.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. @@ -73,6 +73,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest private long connectionRequestTimeout = -1; + /** * Create a new instance of the {@code HttpComponentsClientHttpRequestFactory} * with a default {@link HttpClient} based on system properties. @@ -202,6 +203,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest this.httpContextFactory = httpContextFactory; } + @Override public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { HttpClient client = getHttpClient(); @@ -309,8 +311,8 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest } /** - * Template method that allows for manipulating the {@link ClassicHttpRequest} before it is - * returned as part of a {@link HttpComponentsClientHttpRequest}. + * Template method that allows for manipulating the {@link ClassicHttpRequest} + * before it is returned as part of a {@link HttpComponentsClientHttpRequest}. *

The default implementation is empty. * @param request the request to process */ @@ -331,8 +333,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest /** - * Shutdown hook that closes the underlying - * {@link HttpClientConnectionManager ClientConnectionManager}'s + * Shutdown hook that closes the underlying {@link HttpClientConnectionManager}'s * connection pool, if any. */ @Override diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java index 05a8bebeb24..9555a1c67a8 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorNetty2ResourceFactory.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. @@ -135,7 +135,7 @@ public class ReactorNetty2ResourceFactory implements InitializingBean, Disposabl /** * Use this when you don't want to participate in global resources and * you want to customize the creation of the managed {@code LoopResources}. - *

By default, {@code LoopResources.create("reactor-http")} is used. + *

By default, {@code LoopResources.create("webflux-http")} is used. *

Note that this option is ignored if {@code userGlobalResources=false} or * {@link #setLoopResources(LoopResources)} is set. * @param supplier the supplier to use @@ -170,7 +170,6 @@ public class ReactorNetty2ResourceFactory implements InitializingBean, Disposabl * can also be overridden with the system property * {@link reactor.netty5.ReactorNetty#SHUTDOWN_QUIET_PERIOD * ReactorNetty.SHUTDOWN_QUIET_PERIOD}. - * @since 5.2.4 * @see #setShutdownTimeout(Duration) */ public void setShutdownQuietPeriod(Duration shutdownQuietPeriod) { @@ -187,7 +186,6 @@ public class ReactorNetty2ResourceFactory implements InitializingBean, Disposabl * can also be overridden with the system property * {@link reactor.netty5.ReactorNetty#SHUTDOWN_TIMEOUT * ReactorNetty.SHUTDOWN_TIMEOUT}. - * @since 5.2.4 * @see #setShutdownQuietPeriod(Duration) */ public void setShutdownTimeout(Duration shutdownTimeout) {