diff --git a/spring-context/spring-context.gradle b/spring-context/spring-context.gradle index 0b06a7c56f..30ab5d6e9a 100644 --- a/spring-context/spring-context.gradle +++ b/spring-context/spring-context.gradle @@ -34,8 +34,8 @@ dependencies { testCompile("org.codehaus.groovy:groovy-test") testCompile("org.codehaus.groovy:groovy-xml") testCompile("org.apache.commons:commons-pool2") - testCompile("javax.inject:javax.inject-tck") testCompile("org.awaitility:awaitility") + testCompile("javax.inject:javax.inject-tck") testRuntime("javax.xml.bind:jaxb-api") testRuntime("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/spring-core.gradle b/spring-core/spring-core.gradle index 9e1907a1e8..699da10562 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -53,22 +53,22 @@ dependencies { optional("io.reactivex:rxjava-reactive-streams") optional("io.reactivex.rxjava2:rxjava") optional("io.netty:netty-buffer") - testCompile("io.projectreactor:reactor-test") - testCompile("com.google.code.findbugs:jsr305") + testCompile(project(":kotlin-coroutines")) testCompile("javax.annotation:javax.annotation-api") testCompile("javax.xml.bind:jaxb-api") + testCompile("com.google.code.findbugs:jsr305") testCompile("com.fasterxml.woodstox:woodstox-core") testCompile("org.xmlunit:xmlunit-assertj") testCompile("org.xmlunit:xmlunit-matchers") - testCompile(project(":kotlin-coroutines")) + testCompile("io.projectreactor:reactor-test") testCompile("io.projectreactor.tools:blockhound") - testFixturesImplementation("io.projectreactor:reactor-test") testFixturesImplementation("com.google.code.findbugs:jsr305") testFixturesImplementation("org.junit.platform:junit-platform-launcher") testFixturesImplementation("org.junit.jupiter:junit-jupiter-api") testFixturesImplementation("org.junit.jupiter:junit-jupiter-params") testFixturesImplementation("org.assertj:assertj-core") testFixturesImplementation("org.xmlunit:xmlunit-assertj") + testFixturesImplementation("io.projectreactor:reactor-test") } jar { 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 f3a770163e..70622b7d5c 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 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. @@ -47,7 +47,7 @@ import org.springframework.util.ReflectionUtils; * *

By default, depending on classpath availability, adapters are registered * for Reactor, RxJava 1, RxJava 2 types, {@link CompletableFuture}, Java 9+ - * {@code Flow.Publisher} and Kotlin Coroutines {@code Deferred} and {@code Flow}. + * {@code Flow.Publisher}, and Kotlin Coroutines {@code Deferred} and {@code Flow}. * * @author Rossen Stoyanchev * @author Sebastien Deleuze @@ -74,6 +74,10 @@ public class ReactiveAdapterRegistry { boolean reactorRegistered = false; if (ClassUtils.isPresent("reactor.core.publisher.Flux", classLoader)) { new ReactorRegistrar().registerAdapters(this); + if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) { + // Java 9+ Flow.Publisher + new ReactorJdkFlowAdapterRegistrar().registerAdapter(this); + } reactorRegistered = true; } this.reactorPresent = reactorRegistered; @@ -83,20 +87,12 @@ public class ReactiveAdapterRegistry { ClassUtils.isPresent("rx.RxReactiveStreams", classLoader)) { new RxJava1Registrar().registerAdapters(this); } - // RxJava2 if (ClassUtils.isPresent("io.reactivex.Flowable", classLoader)) { new RxJava2Registrar().registerAdapters(this); } - // Java 9+ Flow.Publisher - if (ClassUtils.isPresent("java.util.concurrent.Flow.Publisher", classLoader)) { - new ReactorJdkFlowAdapterRegistrar().registerAdapter(this); - } - // If not present, do nothing for the time being... - // We can fall back on "reactive-streams-flow-bridge" (once released) - - // Coroutines + // Kotlin Coroutines if (this.reactorPresent && ClassUtils.isPresent("kotlinx.coroutines.reactor.MonoKt", classLoader)) { new CoroutinesRegistrar().registerAdapters(this); } @@ -226,6 +222,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) { @@ -280,35 +305,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 * {@link Mono} depending on {@link ReactiveTypeDescriptor#isMultiValue()}.