Browse Source

Merge branch '6.1.x'

pull/32957/head
Juergen Hoeller 2 years ago
parent
commit
017bf4534d
  1. 6
      spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequest.java
  2. 39
      spring-web/src/main/java/org/springframework/http/client/ReactorResourceFactory.java
  3. 3
      spring-web/src/test/java/org/springframework/http/client/ReactorNettyClientRequestFactoryTests.java
  4. 97
      spring-web/src/test/java/org/springframework/http/client/ReactorResourceFactoryTests.java

6
spring-web/src/main/java/org/springframework/http/client/ReactorNettyClientRequest.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -101,7 +101,7 @@ final class ReactorNettyClientRequest extends AbstractStreamingClientHttpRequest
return result; return result;
} }
} }
catch (RuntimeException ex) { // Exceptions.ReactiveException is package private catch (RuntimeException ex) { // Exceptions.ReactiveException is package private
Throwable cause = ex.getCause(); Throwable cause = ex.getCause();
if (cause instanceof UncheckedIOException uioEx) { if (cause instanceof UncheckedIOException uioEx) {
@ -111,7 +111,7 @@ final class ReactorNettyClientRequest extends AbstractStreamingClientHttpRequest
throw ioEx; throw ioEx;
} }
else { else {
throw ex; throw new IOException(ex.getMessage(), cause);
} }
} }
} }

39
spring-web/src/main/java/org/springframework/http/client/ReactorResourceFactory.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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -26,7 +26,9 @@ import reactor.netty.resources.LoopResources;
import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean; import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.Lifecycle; import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.SmartLifecycle;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
@ -35,20 +37,21 @@ import org.springframework.util.Assert;
* event loop threads, and {@link ConnectionProvider} for the connection pool, * event loop threads, and {@link ConnectionProvider} for the connection pool,
* within the lifecycle of a Spring {@code ApplicationContext}. * within the lifecycle of a Spring {@code ApplicationContext}.
* *
* <p>This factory implements {@link InitializingBean}, {@link DisposableBean} * <p>This factory implements {@link SmartLifecycle} and is expected typically
* and {@link Lifecycle} and is expected typically to be declared as a * to be declared as a Spring-managed bean.
* Spring-managed bean.
* *
* <p>Notice that after a {@link Lifecycle} stop/restart, new instances of * <p>Notice that after a {@link SmartLifecycle} stop/restart, new instances of
* the configured {@link LoopResources} and {@link ConnectionProvider} are * the configured {@link LoopResources} and {@link ConnectionProvider} are
* created, so any references to those should be updated. * created, so any references to those should be updated.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Brian Clozel * @author Brian Clozel
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller
* @since 6.1 * @since 6.1
*/ */
public class ReactorResourceFactory implements InitializingBean, DisposableBean, Lifecycle { public class ReactorResourceFactory
implements ApplicationContextAware, InitializingBean, DisposableBean, SmartLifecycle {
private boolean useGlobalResources = true; private boolean useGlobalResources = true;
@ -73,6 +76,9 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean,
private Duration shutdownTimeout = Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_TIMEOUT); private Duration shutdownTimeout = Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_TIMEOUT);
@Nullable
private ApplicationContext applicationContext;
private volatile boolean running; private volatile boolean running;
private final Object lifecycleMonitor = new Object(); private final Object lifecycleMonitor = new Object();
@ -202,15 +208,30 @@ public class ReactorResourceFactory implements InitializingBean, DisposableBean,
this.shutdownTimeout = shutdownTimeout; this.shutdownTimeout = shutdownTimeout;
} }
/**
* Setting an {@link ApplicationContext} is optional: If set, Reactor resources
* will be initialized in the {@link #start() lifecycle start} phase and closed
* in the {@link #stop() lifecycle stop} phase. If not set, it will happen in
* {@link #afterPropertiesSet()} and {@link #destroy()}, respectively.
*/
@Override
public void setApplicationContext(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
start(); if (this.applicationContext == null) {
start();
}
} }
@Override @Override
public void destroy() { public void destroy() {
stop(); if (this.applicationContext == null) {
stop();
}
} }
@Override @Override

3
spring-web/src/test/java/org/springframework/http/client/ReactorNettyClientHttpRequestFactoryTests.java → spring-web/src/test/java/org/springframework/http/client/ReactorNettyClientRequestFactoryTests.java

@ -28,8 +28,9 @@ import static org.assertj.core.api.Assertions.assertThat;
/** /**
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @since 6.1
*/ */
class ReactorNettyClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTests { class ReactorNettyClientRequestFactoryTests extends AbstractHttpRequestFactoryTests {
@Override @Override
protected ClientHttpRequestFactory createRequestFactory() { protected ClientHttpRequestFactory createRequestFactory() {

97
spring-web/src/test/java/org/springframework/http/client/reactive/ReactorResourceFactoryTests.java → spring-web/src/test/java/org/springframework/http/client/ReactorResourceFactoryTests.java

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.springframework.http.client.reactive; package org.springframework.http.client;
import java.time.Duration; import java.time.Duration;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
@ -24,7 +24,7 @@ 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;
import org.springframework.http.client.ReactorResourceFactory; import org.springframework.context.support.GenericApplicationContext;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@ -37,6 +37,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Juergen Hoeller
*/ */
class ReactorResourceFactoryTests { class ReactorResourceFactoryTests {
@ -49,37 +50,34 @@ class ReactorResourceFactoryTests {
@Test @Test
void globalResources() { void globalResources() {
this.resourceFactory.setUseGlobalResources(true); this.resourceFactory.setUseGlobalResources(true);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
HttpResources globalResources = HttpResources.get(); HttpResources globalResources = HttpResources.get();
assertThat(this.resourceFactory.getConnectionProvider()).isSameAs(globalResources); assertThat(this.resourceFactory.getConnectionProvider()).isSameAs(globalResources);
assertThat(this.resourceFactory.getLoopResources()).isSameAs(globalResources); assertThat(this.resourceFactory.getLoopResources()).isSameAs(globalResources);
assertThat(globalResources.isDisposed()).isFalse(); assertThat(globalResources.isDisposed()).isFalse();
this.resourceFactory.destroy(); this.resourceFactory.stop();
assertThat(globalResources.isDisposed()).isTrue(); assertThat(globalResources.isDisposed()).isTrue();
} }
@Test @Test
void globalResourcesWithConsumer() { void globalResourcesWithConsumer() {
AtomicBoolean invoked = new AtomicBoolean(); AtomicBoolean invoked = new AtomicBoolean();
this.resourceFactory.addGlobalResourcesConsumer(httpResources -> invoked.set(true)); this.resourceFactory.addGlobalResourcesConsumer(httpResources -> invoked.set(true));
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
assertThat(invoked.get()).isTrue(); assertThat(invoked.get()).isTrue();
this.resourceFactory.destroy(); this.resourceFactory.stop();
} }
@Test @Test
void localResources() { void localResources() {
this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.setUseGlobalResources(false);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider(); ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider();
LoopResources loopResources = this.resourceFactory.getLoopResources(); LoopResources loopResources = this.resourceFactory.getLoopResources();
@ -91,7 +89,7 @@ class ReactorResourceFactoryTests {
// assertFalse(connectionProvider.isDisposed()); // assertFalse(connectionProvider.isDisposed());
assertThat(loopResources.isDisposed()).isFalse(); assertThat(loopResources.isDisposed()).isFalse();
this.resourceFactory.destroy(); this.resourceFactory.stop();
assertThat(connectionProvider.isDisposed()).isTrue(); assertThat(connectionProvider.isDisposed()).isTrue();
assertThat(loopResources.isDisposed()).isTrue(); assertThat(loopResources.isDisposed()).isTrue();
@ -99,11 +97,10 @@ class ReactorResourceFactoryTests {
@Test @Test
void localResourcesViaSupplier() { void localResourcesViaSupplier() {
this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.setUseGlobalResources(false);
this.resourceFactory.setConnectionProviderSupplier(() -> this.connectionProvider); this.resourceFactory.setConnectionProviderSupplier(() -> this.connectionProvider);
this.resourceFactory.setLoopResourcesSupplier(() -> this.loopResources); this.resourceFactory.setLoopResourcesSupplier(() -> this.loopResources);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider(); ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider();
LoopResources loopResources = this.resourceFactory.getLoopResources(); LoopResources loopResources = this.resourceFactory.getLoopResources();
@ -113,9 +110,9 @@ class ReactorResourceFactoryTests {
verifyNoMoreInteractions(this.connectionProvider, this.loopResources); verifyNoMoreInteractions(this.connectionProvider, this.loopResources);
this.resourceFactory.destroy(); this.resourceFactory.stop();
// Managed (destroy disposes).. // Managed (stop disposes)..
verify(this.connectionProvider).disposeLater(); verify(this.connectionProvider).disposeLater();
verify(this.loopResources).disposeLater(eq(Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_QUIET_PERIOD)), eq(Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_TIMEOUT))); verify(this.loopResources).disposeLater(eq(Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_QUIET_PERIOD)), eq(Duration.ofSeconds(LoopResources.DEFAULT_SHUTDOWN_TIMEOUT)));
verifyNoMoreInteractions(this.connectionProvider, this.loopResources); verifyNoMoreInteractions(this.connectionProvider, this.loopResources);
@ -130,8 +127,8 @@ class ReactorResourceFactoryTests {
this.resourceFactory.setLoopResourcesSupplier(() -> this.loopResources); this.resourceFactory.setLoopResourcesSupplier(() -> this.loopResources);
this.resourceFactory.setShutdownQuietPeriod(quietPeriod); this.resourceFactory.setShutdownQuietPeriod(quietPeriod);
this.resourceFactory.setShutdownTimeout(shutdownTimeout); this.resourceFactory.setShutdownTimeout(shutdownTimeout);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
this.resourceFactory.destroy(); this.resourceFactory.stop();
verify(this.connectionProvider).disposeLater(); verify(this.connectionProvider).disposeLater();
verify(this.loopResources).disposeLater(eq(quietPeriod), eq(shutdownTimeout)); verify(this.loopResources).disposeLater(eq(quietPeriod), eq(shutdownTimeout));
@ -140,11 +137,10 @@ class ReactorResourceFactoryTests {
@Test @Test
void externalResources() { void externalResources() {
this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.setUseGlobalResources(false);
this.resourceFactory.setConnectionProvider(this.connectionProvider); this.resourceFactory.setConnectionProvider(this.connectionProvider);
this.resourceFactory.setLoopResources(this.loopResources); this.resourceFactory.setLoopResources(this.loopResources);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider(); ConnectionProvider connectionProvider = this.resourceFactory.getConnectionProvider();
LoopResources loopResources = this.resourceFactory.getLoopResources(); LoopResources loopResources = this.resourceFactory.getLoopResources();
@ -154,17 +150,16 @@ class ReactorResourceFactoryTests {
verifyNoMoreInteractions(this.connectionProvider, this.loopResources); verifyNoMoreInteractions(this.connectionProvider, this.loopResources);
this.resourceFactory.destroy(); this.resourceFactory.stop();
// Not managed (destroy has no impact) // Not managed (stop has no impact)
verifyNoMoreInteractions(this.connectionProvider, this.loopResources); verifyNoMoreInteractions(this.connectionProvider, this.loopResources);
} }
@Test @Test
void restartWithGlobalResources() { void restartWithGlobalResources() {
this.resourceFactory.setUseGlobalResources(true); this.resourceFactory.setUseGlobalResources(true);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
this.resourceFactory.stop(); this.resourceFactory.stop();
this.resourceFactory.start(); this.resourceFactory.start();
@ -173,16 +168,15 @@ class ReactorResourceFactoryTests {
assertThat(this.resourceFactory.getLoopResources()).isSameAs(globalResources); assertThat(this.resourceFactory.getLoopResources()).isSameAs(globalResources);
assertThat(globalResources.isDisposed()).isFalse(); assertThat(globalResources.isDisposed()).isFalse();
this.resourceFactory.destroy(); this.resourceFactory.stop();
assertThat(globalResources.isDisposed()).isTrue(); assertThat(globalResources.isDisposed()).isTrue();
} }
@Test @Test
void restartWithLocalResources() { void restartWithLocalResources() {
this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.setUseGlobalResources(false);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
this.resourceFactory.stop(); this.resourceFactory.stop();
this.resourceFactory.start(); this.resourceFactory.start();
@ -196,7 +190,7 @@ class ReactorResourceFactoryTests {
// assertFalse(connectionProvider.isDisposed()); // assertFalse(connectionProvider.isDisposed());
assertThat(loopResources.isDisposed()).isFalse(); assertThat(loopResources.isDisposed()).isFalse();
this.resourceFactory.destroy(); this.resourceFactory.stop();
assertThat(connectionProvider.isDisposed()).isTrue(); assertThat(connectionProvider.isDisposed()).isTrue();
assertThat(loopResources.isDisposed()).isTrue(); assertThat(loopResources.isDisposed()).isTrue();
@ -204,11 +198,10 @@ class ReactorResourceFactoryTests {
@Test @Test
void restartWithExternalResources() { void restartWithExternalResources() {
this.resourceFactory.setUseGlobalResources(false); this.resourceFactory.setUseGlobalResources(false);
this.resourceFactory.setConnectionProvider(this.connectionProvider); this.resourceFactory.setConnectionProvider(this.connectionProvider);
this.resourceFactory.setLoopResources(this.loopResources); this.resourceFactory.setLoopResources(this.loopResources);
this.resourceFactory.afterPropertiesSet(); this.resourceFactory.start();
this.resourceFactory.stop(); this.resourceFactory.stop();
this.resourceFactory.start(); this.resourceFactory.start();
@ -220,10 +213,52 @@ class ReactorResourceFactoryTests {
verifyNoMoreInteractions(this.connectionProvider, this.loopResources); verifyNoMoreInteractions(this.connectionProvider, this.loopResources);
this.resourceFactory.destroy(); this.resourceFactory.stop();
// Not managed (destroy has no impact)... // Not managed (stop has no impact)...
verifyNoMoreInteractions(this.connectionProvider, this.loopResources); verifyNoMoreInteractions(this.connectionProvider, this.loopResources);
} }
@Test
void restartWithinApplicationContext() {
GenericApplicationContext context = new GenericApplicationContext();
context.registerBean(ReactorResourceFactory.class);
context.refresh();
ReactorResourceFactory resourceFactory = context.getBean(ReactorResourceFactory.class);
assertThat(resourceFactory.isRunning()).isTrue();
HttpResources globalResources = HttpResources.get();
assertThat(resourceFactory.getConnectionProvider()).isSameAs(globalResources);
assertThat(resourceFactory.getLoopResources()).isSameAs(globalResources);
assertThat(globalResources.isDisposed()).isFalse();
context.stop();
assertThat(globalResources.isDisposed()).isTrue();
context.start();
globalResources = HttpResources.get();
assertThat(resourceFactory.getConnectionProvider()).isSameAs(globalResources);
assertThat(resourceFactory.getLoopResources()).isSameAs(globalResources);
assertThat(globalResources.isDisposed()).isFalse();
assertThat(globalResources.isDisposed()).isFalse();
context.close();
assertThat(globalResources.isDisposed()).isTrue();
}
@Test
void doNotStartBeforeApplicationContextFinish() {
GenericApplicationContext context = new GenericApplicationContext() {
@Override
protected void finishRefresh() {
}
};
context.registerBean(ReactorResourceFactory.class);
context.refresh();
ReactorResourceFactory resourceFactory = context.getBeanFactory().getBean(ReactorResourceFactory.class);
assertThat(resourceFactory.isRunning()).isFalse();
}
} }
Loading…
Cancel
Save