Refine ReactorResourceFactory

1. Rename globalResources to useGlobalResources.
2. Use of global resources is mutually exlusive with explicit config.
3. Allow Consumer<HttpResources> 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
This commit is contained in:
Rossen Stoyanchev 2018-09-05 21:17:04 -04:00
parent 3302798e2f
commit d537a1cfb4
2 changed files with 93 additions and 54 deletions

View File

@ -15,6 +15,9 @@
*/ */
package org.springframework.http.client.reactive; package org.springframework.http.client.reactive;
import java.util.function.Consumer;
import java.util.function.Supplier;
import reactor.netty.http.HttpResources; import reactor.netty.http.HttpResources;
import reactor.netty.resources.ConnectionProvider; import reactor.netty.resources.ConnectionProvider;
import reactor.netty.resources.LoopResources; import reactor.netty.resources.LoopResources;
@ -37,7 +40,16 @@ import org.springframework.util.Assert;
*/ */
public class ReactorResourceFactory implements InitializingBean, DisposableBean { public class ReactorResourceFactory implements InitializingBean, DisposableBean {
private boolean globalResources = true; private boolean useGlobalResources = true;
@Nullable
private Consumer<HttpResources> globalResourcesConsumer;
private Supplier<ConnectionProvider> connectionProviderSupplier = () -> ConnectionProvider.elastic("http");
private Supplier<LoopResources> loopResourcesSupplier = () -> LoopResources.create("reactor-http");
@Nullable @Nullable
private ConnectionProvider connectionProvider; private ConnectionProvider connectionProvider;
@ -45,110 +57,137 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean
@Nullable @Nullable
private LoopResources loopResources; 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 * Whether to use global Reactor Netty resources via {@link HttpResources}.
* {@link HttpResources} holder. * <p>Default is "true" in which case this factory initializes and stops the
* <p>Default is "true" in which case this factory helps to configure and * global Reactor Netty resources within Spring's {@code ApplicationContext}
* shut down the global Reactor Netty resources within the lifecycle of a * lifecycle. If set to "false" the factory manages its resources independent
* Spring {@code ApplicationContext}. * of the global ones.
* <p>If set to "false" then the factory creates and manages its own * @param useGlobalResources whether to expose and manage the global resources
* {@link LoopResources} and {@link ConnectionProvider}, independent of the * @see #addGlobalResourcesConsumer(Consumer)
* global ones in the {@link HttpResources} holder.
* @param globalResources whether to expose and manage the global resources
*/ */
public void setGlobalResources(boolean globalResources) { public void setUseGlobalResources(boolean useGlobalResources) {
this.globalResources = globalResources; this.useGlobalResources = useGlobalResources;
} }
/** /**
* Configure the {@link ConnectionProvider} to use. * Add a Consumer for configuring the global Reactor Netty resources on
* <p>By default, initialized with {@link ConnectionProvider#elastic(String)}. * startup. When this option is used, {@link #setUseGlobalResources} is also
* @param connectionProvider the connection provider to use * enabled.
* @param consumer the consumer to apply
* @see #setUseGlobalResources(boolean)
*/
public void addGlobalResourcesConsumer(Consumer<HttpResources> consumer) {
this.useGlobalResources = true;
this.globalResourcesConsumer = this.globalResourcesConsumer != null ?
this.globalResourcesConsumer.andThen(consumer) : consumer;
}
/**
* 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}.
* <p>By default, {@code ConnectionProvider.elastic("http")} is used.
* <p>Note that this option is ignored if {@code userGlobalResources=false} or
* {@link #setConnectionProvider(ConnectionProvider)} is set.
* @param supplier the supplier to use
*/
public void setConnectionProviderSupplier(@Nullable Supplier<ConnectionProvider> 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}.
* <p>By default, {@code LoopResources.create("reactor-http")} is used.
* <p>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<LoopResources> 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) { public void setConnectionProvider(@Nullable ConnectionProvider connectionProvider) {
this.connectionProvider = connectionProvider; this.connectionProvider = connectionProvider;
} }
/** /**
* Configure the {@link LoopResources} to use. * Use this option when you want to provide an externally managed
* <p>By default, initialized with {@link LoopResources#create(String)}. * {@link LoopResources} instance.
* @param loopResources the loop resources to use * @param loopResources the loop resources to use as is
*/ */
public void setLoopResources(@Nullable LoopResources loopResources) { public void setLoopResources(@Nullable LoopResources loopResources) {
this.loopResources = loopResources; this.loopResources = loopResources;
} }
/**
* Configure the thread prefix to initialize {@link LoopResources} with. This
* is used only when a {@link LoopResources} instance isn't
* {@link #setLoopResources(LoopResources) provided}.
* <p>By default set to "reactor-http".
* @param threadPrefix the thread prefix to use
*/
public void setThreadPrefix(String threadPrefix) {
Assert.notNull(threadPrefix, "Thread prefix is required");
this.threadPrefix = threadPrefix;
}
/** /**
* Whether this factory exposes the global * Whether this factory exposes the global
* {@link reactor.netty.http.HttpResources HttpResources} holder. * {@link reactor.netty.http.HttpResources HttpResources} holder.
*/ */
public boolean isGlobalResources() { public boolean isUseGlobalResources() {
return this.globalResources; return this.useGlobalResources;
} }
/** /**
* Return the configured {@link ConnectionProvider}. * Return the configured {@link ConnectionProvider}.
*/ */
@Nullable
public ConnectionProvider getConnectionProvider() { public ConnectionProvider getConnectionProvider() {
Assert.notNull(this.connectionProvider, "ConnectionProvider not initialized yet via InitializingBean.");
return this.connectionProvider; return this.connectionProvider;
} }
/** /**
* Return the configured {@link LoopResources}. * Return the configured {@link LoopResources}.
*/ */
@Nullable
public LoopResources getLoopResources() { public LoopResources getLoopResources() {
Assert.notNull(this.loopResources, "LoopResources not initialized yet via InitializingBean.");
return this.loopResources; return this.loopResources;
} }
/**
* Return the configured prefix for event loop threads.
*/
public String getThreadPrefix() {
return this.threadPrefix;
}
@Override @Override
public void afterPropertiesSet() throws Exception { public void afterPropertiesSet() throws Exception {
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);
}
}
else {
if (this.loopResources == null) { if (this.loopResources == null) {
this.loopResources = LoopResources.create(this.threadPrefix); this.manageLoopResources = true;
this.loopResources = this.loopResourcesSupplier.get();
} }
if (this.connectionProvider == null) { if (this.connectionProvider == null) {
this.connectionProvider = ConnectionProvider.elastic("http"); this.manageConnectionProvider = true;
this.connectionProvider = this.connectionProviderSupplier.get();
} }
if (this.globalResources) {
HttpResources.set(this.loopResources);
HttpResources.set(this.connectionProvider);
} }
} }
@Override @Override
public void destroy() { public void destroy() {
if (this.globalResources) { if (this.useGlobalResources) {
HttpResources.disposeLoopsAndConnections(); HttpResources.disposeLoopsAndConnections();
} }
else { else {
try { try {
ConnectionProvider provider = this.connectionProvider; ConnectionProvider provider = this.connectionProvider;
if (provider != null) { if (provider != null && this.manageConnectionProvider) {
provider.dispose(); provider.dispose();
} }
} }
@ -158,7 +197,7 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean
try { try {
LoopResources resources = this.loopResources; LoopResources resources = this.loopResources;
if (resources != null) { if (resources != null && this.manageLoopResources) {
resources.dispose(); resources.dispose();
} }
} }

View File

@ -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 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 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 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: Netty global resources are shut down when the Spring `ApplicationContext` is closed:
[source,java,intent=0] [source,java,intent=0]