diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java index c7e74b2ab2b..28a1152b587 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -135,16 +135,45 @@ public class ReactiveAdapterRegistry { * Register a reactive type along with functions to adapt to and from a * Reactive Streams {@link Publisher}. The function arguments assume that * their input is neither {@code null} nor {@link Optional}. + *

This variant registers the new adapter after existing adapters. + * It will be matched for the exact reactive type if no earlier adapter was + * registered for the specific type, and it will be matched for assignability + * in a second pass if no earlier adapter had an assignable type before. + * @see #registerReactiveTypeOverride + * @see #getAdapter */ public void registerReactiveType(ReactiveTypeDescriptor descriptor, Function> toAdapter, Function, Object> fromAdapter) { - if (reactorPresent) { - this.adapters.add(new ReactorAdapter(descriptor, toAdapter, fromAdapter)); - } - else { - this.adapters.add(new ReactiveAdapter(descriptor, toAdapter, fromAdapter)); - } + this.adapters.add(buildAdapter(descriptor, toAdapter, fromAdapter)); + } + + /** + * Register a reactive type along with functions to adapt to and from a + * Reactive Streams {@link Publisher}. The function arguments assume that + * their input is neither {@code null} nor {@link Optional}. + *

This variant registers the new adapter first, effectively overriding + * any previously registered adapters for the same reactive type. This allows + * for overriding existing adapters, in particular default adapters. + *

Note that existing adapters for specific types will still match before + * an assignability match with the new adapter. In order to override all + * existing matches, a new reactive type adapter needs to be registered + * for every specific type, not relying on subtype assignability matches. + * @since 5.3.30 + * @see #registerReactiveType + * @see #getAdapter + */ + public void registerReactiveTypeOverride(ReactiveTypeDescriptor descriptor, + Function> toAdapter, Function, Object> fromAdapter) { + + this.adapters.add(0, buildAdapter(descriptor, toAdapter, fromAdapter)); + } + + private ReactiveAdapter buildAdapter(ReactiveTypeDescriptor descriptor, + Function> toAdapter, Function, Object> fromAdapter) { + + return (reactorPresent ? new ReactorAdapter(descriptor, toAdapter, fromAdapter) : + new ReactiveAdapter(descriptor, toAdapter, fromAdapter)); } /** diff --git a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java index 98d7d0f59d2..8d4728b2e69 100644 --- a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java +++ b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java @@ -37,6 +37,7 @@ import static org.assertj.core.api.Assertions.assertThat; * Unit tests for {@link ReactiveAdapterRegistry}. * * @author Rossen Stoyanchev + * @author Juergen Hoeller */ @SuppressWarnings("unchecked") class ReactiveAdapterRegistryTests { @@ -52,14 +53,40 @@ class ReactiveAdapterRegistryTests { ReactiveAdapter adapter2 = getAdapter(ExtendedFlux.class); assertThat(adapter2).isSameAs(adapter1); + // Register regular reactive type (after existing adapters) this.registry.registerReactiveType( ReactiveTypeDescriptor.multiValue(ExtendedFlux.class, ExtendedFlux::empty), o -> (ExtendedFlux) o, ExtendedFlux::from); + // Matches for ExtendedFlux itself ReactiveAdapter adapter3 = getAdapter(ExtendedFlux.class); assertThat(adapter3).isNotNull(); assertThat(adapter3).isNotSameAs(adapter1); + + // Does not match for ExtendedFlux subclass since the default Flux adapter + // is being assignability-checked first when no specific match was found + ReactiveAdapter adapter4 = getAdapter(ExtendedExtendedFlux.class); + assertThat(adapter4).isSameAs(adapter1); + + // Register reactive type override (before existing adapters) + this.registry.registerReactiveTypeOverride( + ReactiveTypeDescriptor.multiValue(Flux.class, ExtendedFlux::empty), + o -> (ExtendedFlux) o, + ExtendedFlux::from); + + // Override match for Flux + ReactiveAdapter adapter5 = getAdapter(Flux.class); + assertThat(adapter5).isNotNull(); + assertThat(adapter5).isNotSameAs(adapter1); + + // Initially registered adapter specifically matches for ExtendedFlux + ReactiveAdapter adapter6 = getAdapter(ExtendedFlux.class); + assertThat(adapter6).isSameAs(adapter3); + + // Override match for ExtendedFlux subclass + ReactiveAdapter adapter7 = getAdapter(ExtendedExtendedFlux.class); + assertThat(adapter7).isSameAs(adapter5); } @@ -79,6 +106,10 @@ class ReactiveAdapterRegistryTests { } + private static class ExtendedExtendedFlux extends ExtendedFlux { + } + + @Nested class Reactor {