diff --git a/build.gradle b/build.gradle index aafb9a6c5e..b11a1d4d5d 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ ext { linkScmDevConnection = "scm:git:ssh://git@github.com:spring-projects/spring-framework.git" moduleProjects = subprojects.findAll { - !it.name.equals("spring-build-src") && !it.name.equals("spring-framework-bom") + !it.name.equals("spring-build-src") && !it.name.equals("spring-framework-bom") && !it.name.equals("spring-core-coroutines") } aspectjVersion = "1.9.2" diff --git a/settings.gradle b/settings.gradle index 34f40dc429..907d85a759 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,6 +5,7 @@ include "spring-context" include "spring-context-support" include "spring-context-indexer" include "spring-core" +include "spring-core-coroutines" include "spring-expression" include "spring-instrument" include "spring-jcl" diff --git a/spring-core-coroutines/spring-core-coroutines.gradle b/spring-core-coroutines/spring-core-coroutines.gradle new file mode 100644 index 0000000000..1bff7afbfb --- /dev/null +++ b/spring-core-coroutines/spring-core-coroutines.gradle @@ -0,0 +1,9 @@ +description = "Spring Core Coroutines support" + +dependencies { + compile("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") + compile("org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}") + compile("io.projectreactor:reactor-core") + compile("org.jetbrains.kotlinx:kotlinx-coroutines-core:${coroutinesVersion}") + compile("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:${coroutinesVersion}") +} diff --git a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/result/method/InvocableHandlerMethod.kt b/spring-core-coroutines/src/main/kotlin/org/springframework/core/CoroutinesUtils.kt similarity index 65% rename from spring-webflux/src/main/kotlin/org/springframework/web/reactive/result/method/InvocableHandlerMethod.kt rename to spring-core-coroutines/src/main/kotlin/org/springframework/core/CoroutinesUtils.kt index 1ae5023bd1..befc52ddb0 100644 --- a/spring-webflux/src/main/kotlin/org/springframework/web/reactive/result/method/InvocableHandlerMethod.kt +++ b/spring-core-coroutines/src/main/kotlin/org/springframework/core/CoroutinesUtils.kt @@ -14,10 +14,16 @@ * limitations under the License. */ -package org.springframework.web.reactive.result.method +@file:JvmName("CoroutinesUtils") +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.Mono import reactor.core.publisher.onErrorMap import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method @@ -25,7 +31,23 @@ import kotlin.reflect.full.callSuspend import kotlin.reflect.jvm.kotlinFunction /** - * Invoke an handler method converting suspending method to {@link Mono} if necessary. + * Convert a [Deferred] instance to a [Mono] one. + * + * @author Sebastien Deleuze + * @since 5.2 + */ +internal fun deferredToMono(source: Deferred) = GlobalScope.mono { source.await() } + +/** + * Convert a [Mono] instance to a [Deferred] one. + * + * @author Sebastien Deleuze + * @since 5.2 + */ +internal fun monoToDeferred(source: Mono) = GlobalScope.async { source.awaitFirstOrNull() } + +/** + * Invoke an handler method converting suspending method to [Mono] if necessary. * * @author Sebastien Deleuze * @since 5.2 @@ -40,4 +62,4 @@ internal fun invokeHandlerMethod(method: Method, bean: Any, vararg args: Any?): else { function.call(bean, *args) } -} \ No newline at end of file +} diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle index e24d21057d..a38f986ed9 100644 --- a/spring-core/spring-core.gradle +++ b/spring-core/spring-core.gradle @@ -17,6 +17,9 @@ configurations { jarjar cglib objenesis + coroutines { + transitive = false + } } task cglibRepackJar(type: Jar) { repackJar -> @@ -65,10 +68,12 @@ dependencies { cglib("cglib:cglib:${cglibVersion}@jar") objenesis("org.objenesis:objenesis:${objenesisVersion}@jar") jarjar("org.pantsbuild:jarjar:1.7.2") + coroutines(project(":spring-core-coroutines")) compile(files(cglibRepackJar)) compile(files(objenesisRepackJar)) compile(project(":spring-jcl")) + compileOnly(project(":spring-core-coroutines")) optional("net.sf.jopt-simple:jopt-simple:5.0.4") optional("org.aspectj:aspectjweaver:${aspectjVersion}") optional("org.jetbrains.kotlin:kotlin-reflect:${kotlinVersion}") @@ -78,8 +83,6 @@ 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") @@ -87,6 +90,7 @@ dependencies { testCompile("com.fasterxml.woodstox:woodstox-core:5.2.0") { exclude group: "stax", module: "stax-api" } + testCompile(project(":spring-core-coroutines")) } jar { @@ -107,4 +111,6 @@ jar { from(zipTree(objenesisRepackJar.archivePath)) { include "org/springframework/objenesis/**" } + + from { configurations.coroutines.collect { it.isDirectory() ? it : zipTree(it) } } } 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 b22d24acae..7b9db3c16d 100644 --- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java +++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java @@ -25,6 +25,8 @@ import java.util.function.Function; import io.reactivex.BackpressureStrategy; import io.reactivex.Flowable; +import kotlinx.coroutines.CompletableDeferredKt; +import kotlinx.coroutines.Deferred; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -93,7 +95,7 @@ public class ReactiveAdapterRegistry { // Coroutines if (ClassUtils.isPresent("kotlinx.coroutines.Deferred", classLoader)) { - CoroutinesRegistrarKt.registerAdapter(this); + new CoroutinesRegistrar().registerAdapters(this); } } @@ -324,4 +326,16 @@ public class ReactiveAdapterRegistry { } } + private static class CoroutinesRegistrar { + + @SuppressWarnings("KotlinInternalInJava") + void registerAdapters(ReactiveAdapterRegistry registry) { + registry.registerReactiveType( + ReactiveTypeDescriptor.singleOptionalValue(Deferred.class, () -> CompletableDeferredKt.CompletableDeferred(null)), + source -> CoroutinesUtils.deferredToMono((Deferred) source), + source -> CoroutinesUtils.monoToDeferred(Mono.from(source))); + } + + } + } diff --git a/spring-core/src/main/kotlin/org/springframework/core/CoroutinesRegistrar.kt b/spring-core/src/main/kotlin/org/springframework/core/CoroutinesRegistrar.kt deleted file mode 100644 index 2c9012b66b..0000000000 --- a/spring-core/src/main/kotlin/org/springframework/core/CoroutinesRegistrar.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 - * - * https://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-webflux/spring-webflux.gradle b/spring-webflux/spring-webflux.gradle index 3d8c26f1bc..a82aa320a6 100644 --- a/spring-webflux/spring-webflux.gradle +++ b/spring-webflux/spring-webflux.gradle @@ -13,6 +13,7 @@ dependencies { compile(project(":spring-core")) compile(project(":spring-web")) compile("io.projectreactor:reactor-core") + compileOnly(project(":spring-core-coroutines")) optional(project(":spring-context")) optional(project(":spring-context-support")) // for FreeMarker support optional("javax.servlet:javax.servlet-api:4.0.1") @@ -55,6 +56,7 @@ dependencies { testCompile("org.eclipse.jetty:jetty-reactive-httpclient:1.0.2") testCompile("com.squareup.okhttp3:mockwebserver:3.14.0") testCompile("org.jetbrains.kotlin:kotlin-script-runtime:${kotlinVersion}") + testCompile(project(":spring-core-coroutines")) testRuntime("org.jetbrains.kotlin:kotlin-script-util:${kotlinVersion}") testRuntime("org.jetbrains.kotlin:kotlin-compiler:${kotlinVersion}") testRuntime("org.jruby:jruby:9.2.6.0") diff --git a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java index a62a68d79c..cedc262c5a 100644 --- a/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java +++ b/spring-webflux/src/main/java/org/springframework/web/reactive/result/method/InvocableHandlerMethod.java @@ -26,6 +26,7 @@ import java.util.stream.Stream; import reactor.core.publisher.Mono; +import org.springframework.core.CoroutinesUtils; import org.springframework.core.DefaultParameterNameDiscoverer; import org.springframework.core.KotlinDetector; import org.springframework.core.MethodParameter; @@ -131,6 +132,7 @@ public class InvocableHandlerMethod extends HandlerMethod { * @param providedArgs optional list of argument values to match by type * @return a Mono with a {@link HandlerResult}. */ + @SuppressWarnings("KotlinInternalInJava") public Mono invoke( ServerWebExchange exchange, BindingContext bindingContext, Object... providedArgs) { @@ -140,7 +142,7 @@ public class InvocableHandlerMethod extends HandlerMethod { ReflectionUtils.makeAccessible(getBridgedMethod()); Method method = getBridgedMethod(); if (KotlinDetector.isKotlinReflectPresent() && KotlinDetector.isKotlinType(method.getDeclaringClass())) { - value = InvocableHandlerMethodKt.invokeHandlerMethod(method, getBean(), args); + value = CoroutinesUtils.invokeHandlerMethod(method, getBean(), args); } else { value = method.invoke(getBean(), args);