From baaddae38e9fc7800fe8901f15f46b81355ab4fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Deleuze?= Date: Mon, 20 Oct 2025 16:40:27 +0200 Subject: [PATCH] Convert PropagationContextElement to a Java class In order to avoid having Java types depending on Kotlin types which breaks the compilation in Eclipse IDE. Closes gh-35661 --- .../core/PropagationContextElement.java | 117 ++++++++++++++++++ .../core/PropagationContextElement.kt | 98 --------------- 2 files changed, 117 insertions(+), 98 deletions(-) create mode 100644 spring-core/src/main/java/org/springframework/core/PropagationContextElement.java delete mode 100644 spring-core/src/main/kotlin/org/springframework/core/PropagationContextElement.kt diff --git a/spring-core/src/main/java/org/springframework/core/PropagationContextElement.java b/spring-core/src/main/java/org/springframework/core/PropagationContextElement.java new file mode 100644 index 00000000000..9a2addad645 --- /dev/null +++ b/spring-core/src/main/java/org/springframework/core/PropagationContextElement.java @@ -0,0 +1,117 @@ +/* + * Copyright 2002-present 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 io.micrometer.context.ContextRegistry; +import io.micrometer.context.ContextSnapshot; +import io.micrometer.context.ContextSnapshotFactory; +import kotlin.coroutines.AbstractCoroutineContextElement; +import kotlin.coroutines.CoroutineContext; +import kotlinx.coroutines.ThreadContextElement; +import kotlinx.coroutines.reactor.ReactorContext; +import org.jspecify.annotations.Nullable; +import reactor.util.context.ContextView; + +import org.springframework.util.ClassUtils; + +/** + * {@link ThreadContextElement} that ensures that contexts registered with the + * Micrometer Context Propagation library are captured and restored when + * a coroutine is resumed on a thread. This is typically being used for + * Micrometer Tracing support in Kotlin suspended functions. + * + *

It requires the {@code io.micrometer:context-propagation} library. If the + * {@code org.jetbrains.kotlinx:kotlinx-coroutines-reactor} dependency is also + * on the classpath, this element also supports Reactor {@code Context}. + * + *

{@code PropagationContextElement} can be used like this: + * + *

+ * fun main() {
+ *     runBlocking(Dispatchers.IO + PropagationContextElement()) {
+ *         suspendingFunction()
+ *     }
+ * }
+ *
+ * suspend fun suspendingFunction() {
+ *     delay(1)
+ *     logger.info("Log statement with traceId")
+ * }
+ * 
+ * + * @author Brian Clozel + * @author Sebastien Deleuze + * @since 7.0 + */ +public final class PropagationContextElement extends AbstractCoroutineContextElement implements ThreadContextElement { + + /** + * {@code PropagationContextElement} key. + */ + public static final Key Key = new Key(); + + private static final ContextSnapshotFactory contextSnapshotFactory = ContextSnapshotFactory.builder() + .contextRegistry(ContextRegistry.getInstance()).build(); + + private static final boolean coroutinesReactorPresent = ClassUtils.isPresent("kotlinx.coroutines.reactor.ReactorContext", + PropagationContextElement.class.getClassLoader()); + + private final ContextSnapshot threadLocalContextSnapshot; + + + public PropagationContextElement() { + super(Key); + this.threadLocalContextSnapshot = contextSnapshotFactory.captureAll(); + } + + public void restoreThreadContext(CoroutineContext context, ContextSnapshot.Scope oldState) { + oldState.close(); + } + + public ContextSnapshot.Scope updateThreadContext(CoroutineContext context) { + ContextSnapshot contextSnapshot; + if (coroutinesReactorPresent) { + contextSnapshot = ReactorDelegate.captureFrom(context); + if (contextSnapshot == null) { + contextSnapshot = this.threadLocalContextSnapshot; + } + } + else { + contextSnapshot = this.threadLocalContextSnapshot; + } + return contextSnapshot.setThreadLocals(); + } + + public static final class Key implements CoroutineContext.Key { + } + + private static final class ReactorDelegate { + + @Nullable + @SuppressWarnings({"unchecked", "rawtypes"}) + public static ContextSnapshot captureFrom(CoroutineContext context) { + ReactorContext reactorContext = (ReactorContext)context.get((CoroutineContext.Key)ReactorContext.Key); + ContextView contextView = reactorContext != null ? reactorContext.getContext() : null; + if (contextView != null) { + return contextSnapshotFactory.captureFrom(contextView); + } + else { + return null; + } + } + } +} diff --git a/spring-core/src/main/kotlin/org/springframework/core/PropagationContextElement.kt b/spring-core/src/main/kotlin/org/springframework/core/PropagationContextElement.kt deleted file mode 100644 index ad7ae35948a..00000000000 --- a/spring-core/src/main/kotlin/org/springframework/core/PropagationContextElement.kt +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2002-present 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 io.micrometer.context.ContextRegistry -import io.micrometer.context.ContextSnapshot -import io.micrometer.context.ContextSnapshotFactory -import kotlinx.coroutines.ThreadContextElement -import kotlinx.coroutines.reactor.ReactorContext -import org.springframework.util.ClassUtils -import reactor.util.context.ContextView -import kotlin.coroutines.AbstractCoroutineContextElement -import kotlin.coroutines.CoroutineContext - - -/** - * [ThreadContextElement] that ensures that contexts registered with the - * Micrometer Context Propagation library are captured and restored when - * a coroutine is resumed on a thread. This is typically being used for - * Micrometer Tracing support in Kotlin suspended functions. - * - * It requires the `io.micrometer:context-propagation` library. If the - * `org.jetbrains.kotlinx:kotlinx-coroutines-reactor` dependency is also - * on the classpath, this element also supports Reactor `Context`. - * - * `PropagationContextElement` can be used like this: - * - * ```kotlin - * fun main() { - * runBlocking(Dispatchers.IO + PropagationContextElement()) { - * suspendingFunction() - * } - * } - * - * suspend fun suspendingFunction() { - * delay(1) - * logger.info("Log statement with traceId") - * } - * ``` - * - * @author Brian Clozel - * @author Sebastien Deleuze - * @since 7.0 - */ -class PropagationContextElement : ThreadContextElement, - AbstractCoroutineContextElement(Key) { - - companion object Key : CoroutineContext.Key { - - private val contextSnapshotFactory = - ContextSnapshotFactory.builder().contextRegistry(ContextRegistry.getInstance()).build() - - private val coroutinesReactorPresent = - ClassUtils.isPresent("kotlinx.coroutines.reactor.ReactorContext", - PropagationContextElement::class.java.classLoader); - } - - // Context captured from the the ThreadLocal where the PropagationContextElement is instantiated - private val threadLocalContextSnapshot: ContextSnapshot = contextSnapshotFactory.captureAll() - - override fun restoreThreadContext(context: CoroutineContext, oldState: ContextSnapshot.Scope) { - oldState.close() - } - - override fun updateThreadContext(context: CoroutineContext): ContextSnapshot.Scope { - val contextSnapshot = if (coroutinesReactorPresent) { - ReactorDelegate().captureFrom(context) ?: threadLocalContextSnapshot - } else { - threadLocalContextSnapshot - } - return contextSnapshot.setThreadLocals() - } - - private class ReactorDelegate { - - fun captureFrom(context: CoroutineContext): ContextSnapshot? { - val contextView: ContextView? = context[ReactorContext]?.context - if (contextView != null) { - return contextSnapshotFactory.captureFrom(contextView) - } - return null; - } - } -}