diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index 698ec19ce1..e24d21057d 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -78,6 +78,8 @@ dependencies { optional("io.reactivex:rxjava-reactive-streams:${rxjavaAdapterVersion}") optional("io.reactivex.rxjava2:rxjava:${rxjava2Version}") optional("io.netty:netty-buffer") + optional("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}") + optional("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}") testCompile("io.projectreactor:reactor-test") testCompile("org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}") testCompile("org.xmlunit:xmlunit-matchers:2.6.2") 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 48213cd343..cb7136620b 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -40,8 +40,8 @@ import org.springframework.util.ReflectionUtils; * {@code Observable}, and others. * *
By default, depending on classpath availability, adapters are registered
- * for Reactor, RxJava 1, RxJava 2 types, {@link CompletableFuture}, and Java 9+
- * Flow.Publisher.
+ * for Reactor, RxJava 1, RxJava 2 types, {@link CompletableFuture}, Java 9+
+ * {@code Flow.Publisher} and Kotlin Coroutines {@code Deferred}.
*
* @author Rossen Stoyanchev
* @author Sebastien Deleuze
@@ -90,6 +90,11 @@ public class ReactiveAdapterRegistry {
}
// If not present, do nothing for the time being...
// We can fall back on "reactive-streams-flow-bridge" (once released)
+
+ // Coroutines
+ if (ClassUtils.isPresent("kotlinx.coroutines.Deferred", classLoader)) {
+ CoroutinesRegistrarKt.registerAdapter(this);
+ }
}
diff --git a/spring-core/src/main/kotlin/org/springframework/core/CoroutinesRegistrar.kt b/spring-core/src/main/kotlin/org/springframework/core/CoroutinesRegistrar.kt
new file mode 100644
index 0000000000..4284db11cb
--- /dev/null
+++ b/spring-core/src/main/kotlin/org/springframework/core/CoroutinesRegistrar.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2002-2019 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core
+
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.reactive.awaitFirstOrNull
+import kotlinx.coroutines.reactor.mono
+import reactor.core.publisher.toMono
+
+/**
+ * Register Reactive adapters for Coroutines types.
+ *
+ * @author Sebastien Deleuze
+ * @since 5.2
+ */
+internal fun registerAdapter(registry: ReactiveAdapterRegistry) {
+ registry.registerReactiveType(
+ ReactiveTypeDescriptor.singleOptionalValue(Deferred::class.java) { GlobalScope.async {} },
+ { source -> GlobalScope.mono { (source as Deferred<*>).await() }},
+ { source -> GlobalScope.async { source.toMono().awaitFirstOrNull() } }
+ )
+}
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 ee5a5fd6e5..d019402aa6 100644
--- a/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java
+++ b/spring-core/src/test/java/org/springframework/core/ReactiveAdapterRegistryTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2018 the original author or authors.
+ * Copyright 2002-2019 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.
@@ -23,6 +23,7 @@ import java.util.concurrent.CompletableFuture;
import io.reactivex.Flowable;
import io.reactivex.Maybe;
+import kotlinx.coroutines.Deferred;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
@@ -68,6 +69,9 @@ public class ReactiveAdapterRegistryTests {
assertNotNull(getAdapter(io.reactivex.Single.class));
assertNotNull(getAdapter(Maybe.class));
assertNotNull(getAdapter(io.reactivex.Completable.class));
+
+ // Coroutines
+ assertNotNull(getAdapter(Deferred.class));
}
@Test
diff --git a/spring-core/src/test/kotlin/org/springframework/core/KotlinReactiveAdapterRegistryTests.kt b/spring-core/src/test/kotlin/org/springframework/core/KotlinReactiveAdapterRegistryTests.kt
new file mode 100644
index 0000000000..041b3751e0
--- /dev/null
+++ b/spring-core/src/test/kotlin/org/springframework/core/KotlinReactiveAdapterRegistryTests.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2002-2019 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.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.core
+
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.async
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.reactivestreams.Publisher
+import reactor.core.publisher.Mono
+import java.time.Duration
+import kotlin.reflect.KClass
+
+class KotlinReactiveAdapterRegistryTests {
+
+ private val registry = ReactiveAdapterRegistry.getSharedInstance()
+
+ @Test
+ fun deferredToPublisher() {
+ val source = GlobalScope.async { 1 }
+ val target: Publisher