From d537a1cfb48b628af19d45641be87e6dec2460e8 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Wed, 5 Sep 2018 21:17:04 -0400 Subject: [PATCH] Refine ReactorResourceFactory 1. Rename globalResources to useGlobalResources. 2. Use of global resources is mutually exlusive with explicit config. 3. Allow Consumer to configure global resources. 4. Allow ConnectionProvider + LoopResources Supplier to customize creation and initialization. 5. Do not manage externally provided ConnectionProvider + LoopResources instances. Issue: SPR-17243 --- .../reactive/ReactorResourceFactory.java | 145 +++++++++++------- src/docs/asciidoc/web/webflux-webclient.adoc | 2 +- 2 files changed, 93 insertions(+), 54 deletions(-) diff --git a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorResourceFactory.java b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorResourceFactory.java index 0842ff270c..37b71929a6 100644 --- a/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorResourceFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/reactive/ReactorResourceFactory.java @@ -15,6 +15,9 @@ */ package org.springframework.http.client.reactive; +import java.util.function.Consumer; +import java.util.function.Supplier; + import reactor.netty.http.HttpResources; import reactor.netty.resources.ConnectionProvider; import reactor.netty.resources.LoopResources; @@ -37,7 +40,16 @@ import org.springframework.util.Assert; */ public class ReactorResourceFactory implements InitializingBean, DisposableBean { - private boolean globalResources = true; + private boolean useGlobalResources = true; + + @Nullable + private Consumer globalResourcesConsumer; + + + private Supplier connectionProviderSupplier = () -> ConnectionProvider.elastic("http"); + + private Supplier loopResourcesSupplier = () -> LoopResources.create("reactor-http"); + @Nullable private ConnectionProvider connectionProvider; @@ -45,52 +57,78 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean @Nullable private LoopResources loopResources; - private String threadPrefix = "reactor-http"; + + private boolean manageConnectionProvider = false; + + private boolean manageLoopResources = false; /** - * Whether to expose and manage the global Reactor Netty resources from the - * {@link HttpResources} holder. - *

Default is "true" in which case this factory helps to configure and - * shut down the global Reactor Netty resources within the lifecycle of a - * Spring {@code ApplicationContext}. - *

If set to "false" then the factory creates and manages its own - * {@link LoopResources} and {@link ConnectionProvider}, independent of the - * global ones in the {@link HttpResources} holder. - * @param globalResources whether to expose and manage the global resources + * Whether to use global Reactor Netty resources via {@link HttpResources}. + *

Default is "true" in which case this factory initializes and stops the + * global Reactor Netty resources within Spring's {@code ApplicationContext} + * lifecycle. If set to "false" the factory manages its resources independent + * of the global ones. + * @param useGlobalResources whether to expose and manage the global resources + * @see #addGlobalResourcesConsumer(Consumer) */ - public void setGlobalResources(boolean globalResources) { - this.globalResources = globalResources; + public void setUseGlobalResources(boolean useGlobalResources) { + this.useGlobalResources = useGlobalResources; } /** - * Configure the {@link ConnectionProvider} to use. - *

By default, initialized with {@link ConnectionProvider#elastic(String)}. - * @param connectionProvider the connection provider to use + * Add a Consumer for configuring the global Reactor Netty resources on + * startup. When this option is used, {@link #setUseGlobalResources} is also + * enabled. + * @param consumer the consumer to apply + * @see #setUseGlobalResources(boolean) */ - public void setConnectionProvider(@Nullable ConnectionProvider connectionProvider) { - this.connectionProvider = connectionProvider; + public void addGlobalResourcesConsumer(Consumer consumer) { + this.useGlobalResources = true; + this.globalResourcesConsumer = this.globalResourcesConsumer != null ? + this.globalResourcesConsumer.andThen(consumer) : consumer; } /** - * Configure the {@link LoopResources} to use. - *

By default, initialized with {@link LoopResources#create(String)}. - * @param loopResources the loop resources to use + * Use this option when you don't want to participate in global resources and + * you want to customize the creation of the managed {@code ConnectionProvider}. + *

By default, {@code ConnectionProvider.elastic("http")} is used. + *

Note that this option is ignored if {@code userGlobalResources=false} or + * {@link #setConnectionProvider(ConnectionProvider)} is set. + * @param supplier the supplier to use */ - public void setLoopResources(@Nullable LoopResources loopResources) { - this.loopResources = loopResources; + public void setConnectionProviderSupplier(@Nullable Supplier supplier) { + this.connectionProviderSupplier = supplier; + } + + /** + * Use this option 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. + *

Note that this option is ignored if {@code userGlobalResources=false} or + * {@link #setLoopResources(LoopResources)} is set. + * @param supplier the supplier to use + */ + public void setLoopResourcesSupplier(@Nullable Supplier supplier) { + this.loopResourcesSupplier = supplier; + } + + /** + * Use this option when you want to provide an externally managed + * {@link ConnectionProvider} instance. + * @param connectionProvider the connection provider to use as is + */ + public void setConnectionProvider(@Nullable ConnectionProvider connectionProvider) { + this.connectionProvider = connectionProvider; } /** - * Configure the thread prefix to initialize {@link LoopResources} with. This - * is used only when a {@link LoopResources} instance isn't - * {@link #setLoopResources(LoopResources) provided}. - *

By default set to "reactor-http". - * @param threadPrefix the thread prefix to use + * Use this option when you want to provide an externally managed + * {@link LoopResources} instance. + * @param loopResources the loop resources to use as is */ - public void setThreadPrefix(String threadPrefix) { - Assert.notNull(threadPrefix, "Thread prefix is required"); - this.threadPrefix = threadPrefix; + public void setLoopResources(@Nullable LoopResources loopResources) { + this.loopResources = loopResources; } @@ -98,57 +136,58 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean * Whether this factory exposes the global * {@link reactor.netty.http.HttpResources HttpResources} holder. */ - public boolean isGlobalResources() { - return this.globalResources; + public boolean isUseGlobalResources() { + return this.useGlobalResources; } /** * Return the configured {@link ConnectionProvider}. */ - @Nullable public ConnectionProvider getConnectionProvider() { + Assert.notNull(this.connectionProvider, "ConnectionProvider not initialized yet via InitializingBean."); return this.connectionProvider; } /** * Return the configured {@link LoopResources}. */ - @Nullable public LoopResources getLoopResources() { + Assert.notNull(this.loopResources, "LoopResources not initialized yet via InitializingBean."); return this.loopResources; } - /** - * Return the configured prefix for event loop threads. - */ - public String getThreadPrefix() { - return this.threadPrefix; - } - @Override public void afterPropertiesSet() throws Exception { - if (this.loopResources == null) { - this.loopResources = LoopResources.create(this.threadPrefix); - } - if (this.connectionProvider == null) { - this.connectionProvider = ConnectionProvider.elastic("http"); + if (this.useGlobalResources) { + Assert.isTrue(this.loopResources == null && this.connectionProvider == null, + "'useGlobalResources' is mutually exclusive with explicitly configured resources."); + HttpResources httpResources = HttpResources.get(); + if (this.globalResourcesConsumer != null) { + this.globalResourcesConsumer.accept(httpResources); + } } - if (this.globalResources) { - HttpResources.set(this.loopResources); - HttpResources.set(this.connectionProvider); + else { + if (this.loopResources == null) { + this.manageLoopResources = true; + this.loopResources = this.loopResourcesSupplier.get(); + } + if (this.connectionProvider == null) { + this.manageConnectionProvider = true; + this.connectionProvider = this.connectionProviderSupplier.get(); + } } } @Override public void destroy() { - if (this.globalResources) { + if (this.useGlobalResources) { HttpResources.disposeLoopsAndConnections(); } else { try { ConnectionProvider provider = this.connectionProvider; - if (provider != null) { + if (provider != null && this.manageConnectionProvider) { provider.dispose(); } } @@ -158,7 +197,7 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean try { LoopResources resources = this.loopResources; - if (resources != null) { + if (resources != null && this.manageLoopResources) { resources.dispose(); } } diff --git a/src/docs/asciidoc/web/webflux-webclient.adoc b/src/docs/asciidoc/web/webflux-webclient.adoc index 46ed9dc9a0..d987982d39 100644 --- a/src/docs/asciidoc/web/webflux-webclient.adoc +++ b/src/docs/asciidoc/web/webflux-webclient.adoc @@ -94,7 +94,7 @@ concurrency. In this mode global resources remain active until the process exits If the server is timed with the process, there is typically no need for an explicit shutdown. However if the server can start or stop in-process, e.g. Spring MVC application deployed as a WAR, you can declare a Spring-managed bean of type -`ReactorResourceFactory` with `globalResources=true` (the default) to ensure the Reactor +`ReactorResourceFactory` with `useGlobalResources=true` (the default) to ensure the Reactor Netty global resources are shut down when the Spring `ApplicationContext` is closed: [source,java,intent=0]