diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index 221e0f12fe..fe3faea9f9 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -34,8 +34,8 @@ dependencies { testImplementation("org.codehaus.groovy:groovy-test") testImplementation("org.codehaus.groovy:groovy-xml") testImplementation("org.apache.commons:commons-pool2") - testImplementation("javax.inject:javax.inject-tck") testImplementation("org.awaitility:awaitility") + testImplementation("javax.inject:javax.inject-tck") testRuntimeOnly("javax.xml.bind:jaxb-api") testRuntimeOnly("org.glassfish:javax.el") // Substitute for javax.management:jmxremote_optional:1.0.1_04 (not available on Maven Central) diff --git a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java index 0aaf4c00a0..420de4c5bd 100644 --- a/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java +++ b/spring-context/src/test/java/org/springframework/context/annotation/ConfigurationClassPostConstructAndAutowiringTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2021 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. @@ -25,7 +25,6 @@ import org.springframework.beans.testfixture.beans.TestBean; import static org.assertj.core.api.Assertions.assertThat; - /** * Tests cornering the issue reported in SPR-8080. If the product of a @Bean method * was @Autowired into a configuration class while at the same time the declaring @@ -34,7 +33,7 @@ import static org.assertj.core.api.Assertions.assertThat; * 'currently in creation' status of the autowired bean and result in creating multiple * instances of the given @Bean, violating container scoping / singleton semantics. * - * This is resolved through no longer relying on 'currently in creation' status, but + *
This is resolved through no longer relying on 'currently in creation' status, but * rather on a thread local that informs the enhanced bean method implementation whether * the factory is the caller or not. * 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 cfeaffcaf7..cbed55a289 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -77,11 +77,11 @@ public class ReactiveAdapterRegistry { static { ClassLoader classLoader = ReactiveAdapterRegistry.class.getClassLoader(); reactorPresent = ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader); + flowPublisherPresent = ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader); rxjava1Present = ClassUtils.isPresent("rx.Observable", classLoader) && ClassUtils.isPresent("rx.RxReactiveStreams", classLoader); rxjava2Present = ClassUtils.isPresent("io.reactivex.Flowable", classLoader); rxjava3Present = ClassUtils.isPresent("io.reactivex.rxjava3.core.Flowable", classLoader); - flowPublisherPresent = ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader); kotlinCoroutinesPresent = ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader); mutinyPresent = ClassUtils.isPresent("io.smallrye.mutiny.Multi", classLoader); } @@ -97,13 +97,16 @@ public class ReactiveAdapterRegistry { // Reactor if (reactorPresent) { new ReactorRegistrar().registerAdapters(this); + if (flowPublisherPresent) { + // Java 9+ Flow.Publisher + new ReactorJdkFlowAdapterRegistrar().registerAdapter(this); + } } // RxJava1 (deprecated) if (rxjava1Present) { new RxJava1Registrar().registerAdapters(this); } - // RxJava2 if (rxjava2Present) { new RxJava2Registrar().registerAdapters(this); @@ -113,13 +116,6 @@ public class ReactiveAdapterRegistry { new RxJava3Registrar().registerAdapters(this); } - // Java 9+ Flow.Publisher - if (flowPublisherPresent) { - new ReactorJdkFlowAdapterRegistrar().registerAdapter(this); - } - // If not present, do nothing for the time being... - // We can fall back on "reactive-streams-flow-bridge" (once released) - // Kotlin Coroutines if (reactorPresent && kotlinCoroutinesPresent) { new CoroutinesRegistrar().registerAdapters(this); @@ -253,6 +249,35 @@ public class ReactiveAdapterRegistry { } + private static class ReactorJdkFlowAdapterRegistrar { + + void registerAdapter(ReactiveAdapterRegistry registry) { + // Reflectively access optional JDK 9+ API (for runtime compatibility with JDK 8) + + try { + String publisherName = "java.util.concurrent.Flow.Publisher"; + Class> publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader()); + + String adapterName = "reactor.adapter.JdkFlowAdapter"; + Class> flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader()); + + Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass); + Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class); + Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty()); + + registry.registerReactiveType( + ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow), + source -> (Publisher>) ReflectionUtils.invokeMethod(toFluxMethod, null, source), + publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher) + ); + } + catch (Throwable ex) { + // Ignore + } + } + } + + private static class RxJava1Registrar { void registerAdapters(ReactiveAdapterRegistry registry) { @@ -307,6 +332,7 @@ public class ReactiveAdapterRegistry { } } + private static class RxJava3Registrar { void registerAdapters(ReactiveAdapterRegistry registry) { @@ -347,34 +373,6 @@ public class ReactiveAdapterRegistry { } } - private static class ReactorJdkFlowAdapterRegistrar { - - void registerAdapter(ReactiveAdapterRegistry registry) { - // TODO: remove reflection when build requires JDK 9+ - - try { - String publisherName = "java.util.concurrent.Flow.Publisher"; - Class> publisherClass = ClassUtils.forName(publisherName, getClass().getClassLoader()); - - String adapterName = "reactor.adapter.JdkFlowAdapter"; - Class> flowAdapterClass = ClassUtils.forName(adapterName, getClass().getClassLoader()); - - Method toFluxMethod = flowAdapterClass.getMethod("flowPublisherToFlux", publisherClass); - Method toFlowMethod = flowAdapterClass.getMethod("publisherToFlowPublisher", Publisher.class); - Object emptyFlow = ReflectionUtils.invokeMethod(toFlowMethod, null, Flux.empty()); - - registry.registerReactiveType( - ReactiveTypeDescriptor.multiValue(publisherClass, () -> emptyFlow), - source -> (Publisher>) ReflectionUtils.invokeMethod(toFluxMethod, null, source), - publisher -> ReflectionUtils.invokeMethod(toFlowMethod, null, publisher) - ); - } - catch (Throwable ex) { - // Ignore - } - } - } - /** * ReactiveAdapter variant that wraps adapted Publishers as {@link Flux} or