From bd9f76c7ec3a616ca8bf5c89fa8d5c450acccc1c Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Tue, 31 Mar 2026 19:46:21 +0800 Subject: [PATCH 01/14] Add registry address capture for Dubbo --- .../v2_7/DubboInstrumentationModule.java | 4 + .../v2_7/RegistryAddressCaptureFilter.java | 25 ++ .../META-INF/org.apache.dubbo.rpc.Filter | 1 + .../apachedubbo/v2_7/DubboRequest.java | 2 +- .../v2_7/RegistryAddressCaptureFilter.java | 32 ++ .../DubboClientNetworkAttributesGetter.java | 9 + .../v2_7/internal/DubboRegistryUtil.java | 269 ++++++++++++++++ .../services/org.apache.dubbo.rpc.Filter | 1 + .../v2_7/DubboRegistryUtilTest.java | 301 ++++++++++++++++++ 9 files changed, 643 insertions(+), 1 deletion(-) create mode 100644 instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java index b353997ea93b..1aa75f87312e 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java @@ -42,6 +42,10 @@ public ElementMatcher.Junction classLoaderMatcher() { @Override public void injectClasses(ClassInjector injector) { + injector + .proxyBuilder( + "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter") + .inject(InjectionMode.CLASS_ONLY); injector .proxyBuilder( "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter") diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java new file mode 100644 index 000000000000..2d246091a5af --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +@Activate( + group = {"consumer"}, + order = -10001) +public final class RegistryAddressCaptureFilter implements Filter { + + private final Filter delegate = + new io.opentelemetry.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter(); + + @Override + public Result invoke(Invoker invoker, Invocation invocation) { + return delegate.invoke(invoker, invocation); + } +} diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/org.apache.dubbo.rpc.Filter b/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/org.apache.dubbo.rpc.Filter index f76248783a71..8df6966f4758 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/org.apache.dubbo.rpc.Filter +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/org.apache.dubbo.rpc.Filter @@ -1,2 +1,3 @@ +io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryServerFilter diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java index 0733b744c867..a80544336d82 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java @@ -26,7 +26,7 @@ static DubboRequest create(RpcInvocation invocation, RpcContext context) { context.getLocalAddress()); } - abstract RpcInvocation invocation(); + public abstract RpcInvocation invocation(); public abstract RpcContext context(); diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java new file mode 100644 index 000000000000..7126625be994 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7; + +import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcInvocation; + +@Activate( + group = {"consumer"}, + order = -10001) +public final class RegistryAddressCaptureFilter implements Filter { + + @Override + public Result invoke(Invoker invoker, Invocation invocation) { + if (invocation instanceof RpcInvocation) { + DubboRegistryUtil.captureRegistryAddress((RpcInvocation) invocation); + } + try { + return invoker.invoke(invocation); + } finally { + DubboRegistryUtil.clearCapturedRegistryAddress(); + } + } +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java index 1077ee19c7ce..783b517e8299 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java @@ -22,11 +22,20 @@ public final class DubboClientNetworkAttributesGetter @Nullable @Override public String getServerAddress(DubboRequest request) { + String registryAddress = + DubboRegistryUtil.extractRegistryAddress(request.invocation()); + if (registryAddress != null) { + return registryAddress + "/" + DubboRegistryUtil.buildServiceTarget(request.url()); + } return request.url().getHost(); } + @Nullable @Override public Integer getServerPort(DubboRequest request) { + if (DubboRegistryUtil.extractRegistryAddress(request.invocation()) != null) { + return null; + } return request.url().getPort(); } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java new file mode 100644 index 000000000000..812e82e52208 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -0,0 +1,269 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.cluster.Directory; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class DubboRegistryUtil { + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + private static final Map, Optional> DIRECTORY_ACCESSOR_CACHE = + new ConcurrentHashMap<>(); + private static final Map, Optional> REGISTRY_ACCESSOR_CACHE = + new ConcurrentHashMap<>(); + private static final Map, Optional> URL_ACCESSOR_CACHE = + new ConcurrentHashMap<>(); + + private static final ThreadLocal CAPTURED_REGISTRY_ADDRESS = new ThreadLocal<>(); + + /** + * Called by RegistryAddressCaptureFilter (order -10001) before ConsumerContextFilter overwrites + * invocation.getInvoker(). In Dubbo 2.7.5+ and 3.x the invoker at this point is the cluster + * invoker with getDirectory(). + */ + public static void captureRegistryAddress(RpcInvocation invocation) { + Invoker invoker = invocation.getInvoker(); + if (invoker == null) { + return; + } + Directory directory = getDirectory(invoker); + if (directory != null) { + String address = extractRegistryAddressFromDirectory(directory); + if (address != null) { + CAPTURED_REGISTRY_ADDRESS.set(address); + } + } + } + + public static void clearCapturedRegistryAddress() { + CAPTURED_REGISTRY_ADDRESS.remove(); + } + + @Nullable + public static String extractRegistryAddress(RpcInvocation invocation) { + // 1. Check ThreadLocal (set by RegistryAddressCaptureFilter for Dubbo 2.7.5+ and 3.x) + String captured = CAPTURED_REGISTRY_ADDRESS.get(); + if (captured != null) { + return captured; + } + + Invoker invoker = invocation.getInvoker(); + if (invoker == null) { + return null; + } + + // 2. Try invoker reflection (Dubbo 3.x fallback if capture filter didn't run) + Directory directory = getDirectory(invoker); + if (directory != null) { + String address = extractRegistryAddressFromDirectory(directory); + if (address != null) { + return address; + } + } + + // 3. Try ProviderConsumerRegTable (Dubbo 2.7.0-2.7.4) + return RegTableLookup.extractFromRegTable(invoker.getUrl()); + } + + public static String buildServiceTarget(URL url) { + String interfaceName = url.getServiceInterface(); + if (interfaceName == null || interfaceName.isEmpty()) { + interfaceName = url.getPath(); + } + if (interfaceName == null || interfaceName.isEmpty()) { + return ""; + } + + String version = url.getParameter("version"); + String group = url.getParameter("group"); + boolean hasVersion = version != null && !version.isEmpty(); + boolean hasGroup = group != null && !group.isEmpty(); + + if (!hasVersion && !hasGroup) { + return interfaceName; + } + + StringBuilder sb = new StringBuilder(interfaceName); + sb.append(':'); + if (hasVersion) { + sb.append(version); + } + if (hasGroup) { + sb.append(':').append(group); + } + return sb.toString(); + } + + @Nullable + private static Directory getDirectory(Invoker invoker) { + MethodHandle mh = + findAccessor(invoker.getClass(), "getDirectory", "directory", DIRECTORY_ACCESSOR_CACHE); + if (mh == null) { + return null; + } + try { + Object obj = mh.invoke(invoker); + return obj instanceof Directory ? (Directory) obj : null; + } catch (Throwable t) { + return null; + } + } + + @Nullable + private static String extractRegistryAddressFromDirectory(Directory directory) { + MethodHandle getRegistry = + findAccessor(directory.getClass(), "getRegistry", "registry", REGISTRY_ACCESSOR_CACHE); + if (getRegistry == null) { + return null; + } + try { + Object registry = getRegistry.invoke(directory); + if (registry == null) { + return null; + } + MethodHandle getUrl = + findAccessor(registry.getClass(), "getUrl", null, URL_ACCESSOR_CACHE); + if (getUrl == null) { + return null; + } + Object urlObj = getUrl.invoke(registry); + if (!(urlObj instanceof URL)) { + return null; + } + URL url = (URL) urlObj; + return url.getProtocol() + "://" + url.getAddress(); + } catch (Throwable t) { + return null; + } + } + + @Nullable + private static MethodHandle findAccessor( + Class clazz, + String methodName, + @Nullable String fieldName, + Map, Optional> cache) { + return cache + .computeIfAbsent( + clazz, + cls -> { + MethodHandle mh = resolveMethod(cls, methodName); + if (mh != null) { + return Optional.of(mh); + } + if (fieldName != null) { + mh = resolveField(cls, fieldName); + if (mh != null) { + return Optional.of(mh); + } + } + return Optional.empty(); + }) + .orElse(null); + } + + @Nullable + private static MethodHandle resolveMethod(Class clazz, String name) { + try { + Method m = clazz.getMethod(name); + m.setAccessible(true); + return LOOKUP.unreflect(m); + } catch (NoSuchMethodException | IllegalAccessException e) { + return null; + } + } + + @Nullable + private static MethodHandle resolveField(Class clazz, String name) { + for (Class c = clazz; c != null && c != Object.class; c = c.getSuperclass()) { + try { + Field f = c.getDeclaredField(name); + f.setAccessible(true); + return LOOKUP.unreflectGetter(f); + } catch (NoSuchFieldException | IllegalAccessException e) { + // continue up the hierarchy + } + } + return null; + } + + /** + * Lazily-initialized holder for ProviderConsumerRegTable access. This class only exists in Dubbo + * 2.7.0-2.7.4; all reflection is wrapped to fail gracefully on Dubbo 2.7.5+ and 3.x. + */ + private static class RegTableLookup { + private static final MethodHandle CONSUMER_INVOKERS_GETTER; + private static final MethodHandle GET_REGISTRY_URL; + + static { + MethodHandle consumerInvokersGetter = null; + MethodHandle getRegistryUrl = null; + try { + Class tableClass = + Class.forName("org.apache.dubbo.registry.support.ProviderConsumerRegTable"); + Field f = tableClass.getField("consumerInvokers"); + consumerInvokersGetter = LOOKUP.unreflectGetter(f); + + Class wrapperClass = + Class.forName("org.apache.dubbo.registry.support.ConsumerInvokerWrapper"); + Method m = wrapperClass.getMethod("getRegistryUrl"); + getRegistryUrl = LOOKUP.unreflect(m); + } catch (Throwable ignored) { + // ProviderConsumerRegTable not available (Dubbo 2.7.5+ or 3.x) + } + CONSUMER_INVOKERS_GETTER = consumerInvokersGetter; + GET_REGISTRY_URL = getRegistryUrl; + } + + @Nullable + @SuppressWarnings("unchecked") // ConcurrentHashMap generic cast from reflective access + static String extractFromRegTable(URL invokerUrl) { + if (CONSUMER_INVOKERS_GETTER == null || GET_REGISTRY_URL == null) { + return null; + } + try { + String serviceKey = invokerUrl.getServiceKey(); + if (serviceKey == null || serviceKey.isEmpty()) { + return null; + } + ConcurrentHashMap> table = + (ConcurrentHashMap>) CONSUMER_INVOKERS_GETTER.invoke(); + Set wrappers = table.get(serviceKey); + if (wrappers == null || wrappers.isEmpty()) { + return null; + } + Object wrapper = wrappers.iterator().next(); + Object urlObj = GET_REGISTRY_URL.invoke(wrapper); + if (!(urlObj instanceof URL)) { + return null; + } + URL registryUrl = (URL) urlObj; + return registryUrl.getProtocol() + "://" + registryUrl.getAddress(); + } catch (Throwable t) { + return null; + } + } + } + + private DubboRegistryUtil() {} +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter index 250516c0dc0b..9d88724e4bca 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter @@ -1,2 +1,3 @@ +io.opentelemetry.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter io.opentelemetry.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter io.opentelemetry.instrumentation.apachedubbo.v2_7.OpenTelemetryServerFilter diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java new file mode 100644 index 000000000000..aabfaf44057f --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java @@ -0,0 +1,301 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7; + +import static java.util.Collections.emptyList; +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil; +import java.util.List; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.cluster.Directory; +import org.apache.dubbo.rpc.cluster.directory.StaticDirectory; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") +class DubboRegistryUtilTest { + + private static final URL DUMMY_URL = + URL.valueOf("dubbo://192.168.1.100:20880/com.example.Service"); + + @Test + void extractRegistryAddress_returnsAddressForRegistryDirectory() { + FakeClusterInvoker invoker = new FakeClusterInvoker(new FakeRegistryDirectory(), DUMMY_URL); + RpcInvocation invocation = new RpcInvocation(); + invocation.setInvoker(invoker); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)) + .isEqualTo("nacos://127.0.0.1:8848"); + } + + @Test + void extractRegistryAddress_returnsNullForStaticDirectory() { + StubInvoker stub = new StubInvoker(DUMMY_URL); + StaticDirectory staticDir = new StaticDirectory<>(DUMMY_URL, singletonList(stub)); + FakeClusterInvoker invoker = new FakeClusterInvoker(staticDir, DUMMY_URL); + RpcInvocation invocation = new RpcInvocation(); + invocation.setInvoker(invoker); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); + } + + @Test + void extractRegistryAddress_returnsNullWhenNoInvoker() { + RpcInvocation invocation = new RpcInvocation(); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); + } + + @Test + void extractRegistryAddress_returnsNullWhenInvokerHasNoDirectory() { + RpcInvocation invocation = new RpcInvocation(); + invocation.setInvoker(new StubInvoker(DUMMY_URL)); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); + } + + @Test + void extractRegistryAddress_fieldFallback_returnsAddressForClusterInvokerWithFieldOnly() { + FieldOnlyClusterInvoker invoker = + new FieldOnlyClusterInvoker(new FieldOnlyRegistryDirectory(), DUMMY_URL); + RpcInvocation invocation = new RpcInvocation(); + invocation.setInvoker(invoker); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)) + .isEqualTo("zookeeper://10.0.0.1:2181"); + } + + @Test + void buildServiceTarget_interfaceOnly() { + URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService"); + assertThat(DubboRegistryUtil.buildServiceTarget(url)).isEqualTo("com.example.HelloService"); + } + + @Test + void buildServiceTarget_withVersion() { + URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService?version=2.0.0"); + assertThat(DubboRegistryUtil.buildServiceTarget(url)) + .isEqualTo("com.example.HelloService:2.0.0"); + } + + @Test + void buildServiceTarget_withVersionAndGroup() { + URL url = + URL.valueOf( + "dubbo://192.168.1.100:20880/com.example.HelloService?version=2.0.0&group=gray"); + assertThat(DubboRegistryUtil.buildServiceTarget(url)) + .isEqualTo("com.example.HelloService:2.0.0:gray"); + } + + @Test + void buildServiceTarget_withGroupOnly() { + URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService?group=gray"); + assertThat(DubboRegistryUtil.buildServiceTarget(url)) + .isEqualTo("com.example.HelloService::gray"); + } + + @Test + void buildServiceTarget_withEmptyVersionAndGroup() { + URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService?version=&group="); + assertThat(DubboRegistryUtil.buildServiceTarget(url)).isEqualTo("com.example.HelloService"); + } + + @SuppressWarnings({"unused", "UnusedMethod", "MethodCanBeStatic", "EffectivelyPrivate"}) + private static class FakeClusterInvoker implements Invoker { + + private final Directory directory; + private final URL url; + + FakeClusterInvoker(Directory directory, URL url) { + this.directory = directory; + this.url = url; + } + + public Directory getDirectory() { + return directory; + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public Result invoke(Invocation invocation) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + private static class StubInvoker implements Invoker { + + private final URL url; + + StubInvoker(URL url) { + this.url = url; + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public Result invoke(Invocation invocation) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + @SuppressWarnings({ + "rawtypes", + "unchecked", + "EffectivelyPrivate", + "UnusedMethod", + "MethodCanBeStatic" + }) + private static class FakeRegistryDirectory implements Directory { + + public FakeRegistry getRegistry() { + return new FakeRegistry(); + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public List> list(Invocation invocation) { + return emptyList(); + } + + @Override + public List> getAllInvokers() { + return emptyList(); + } + + @Override + public URL getUrl() { + return URL.valueOf("dubbo://localhost:20880/com.example.Service"); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + static class FakeRegistry { + @SuppressWarnings("unused") + public URL getUrl() { + return URL.valueOf("nacos://127.0.0.1:8848/org.apache.dubbo.registry.RegistryService"); + } + } + + @SuppressWarnings({"unused", "EffectivelyPrivate"}) + private static class FieldOnlyClusterInvoker implements Invoker { + + private final Directory directory; + private final URL url; + + FieldOnlyClusterInvoker(Directory directory, URL url) { + this.directory = directory; + this.url = url; + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public Result invoke(Invocation invocation) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + @SuppressWarnings({"rawtypes", "unchecked", "unused", "EffectivelyPrivate"}) + private static class FieldOnlyRegistryDirectory implements Directory { + + private final FakeZkRegistry registry = new FakeZkRegistry(); + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public List> list(Invocation invocation) { + return emptyList(); + } + + @Override + public List> getAllInvokers() { + return emptyList(); + } + + @Override + public URL getUrl() { + return URL.valueOf("dubbo://localhost:20880/com.example.Service"); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + static class FakeZkRegistry { + @SuppressWarnings("unused") + public URL getUrl() { + return URL.valueOf("zookeeper://10.0.0.1:2181/org.apache.dubbo.registry.RegistryService"); + } + } +} From b390c634fac444bb19a15268324a164923518cd1 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Wed, 1 Apr 2026 23:46:45 +0800 Subject: [PATCH 02/14] Apply spotless --- .../apachedubbo/v2_7/RegistryAddressCaptureFilter.java | 1 + .../internal/DubboClientNetworkAttributesGetter.java | 3 +-- .../apachedubbo/v2_7/internal/DubboRegistryUtil.java | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java index 2d246091a5af..1379807b2502 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java @@ -10,6 +10,7 @@ import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; + @Activate( group = {"consumer"}, order = -10001) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java index 783b517e8299..391eb5f5f438 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java @@ -22,8 +22,7 @@ public final class DubboClientNetworkAttributesGetter @Nullable @Override public String getServerAddress(DubboRequest request) { - String registryAddress = - DubboRegistryUtil.extractRegistryAddress(request.invocation()); + String registryAddress = DubboRegistryUtil.extractRegistryAddress(request.invocation()); if (registryAddress != null) { return registryAddress + "/" + DubboRegistryUtil.buildServiceTarget(request.url()); } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index 812e82e52208..8fccf09256a9 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -141,8 +141,7 @@ private static String extractRegistryAddressFromDirectory(Directory directory if (registry == null) { return null; } - MethodHandle getUrl = - findAccessor(registry.getClass(), "getUrl", null, URL_ACCESSOR_CACHE); + MethodHandle getUrl = findAccessor(registry.getClass(), "getUrl", null, URL_ACCESSOR_CACHE); if (getUrl == null) { return null; } @@ -252,6 +251,12 @@ static String extractFromRegTable(URL invokerUrl) { if (wrappers == null || wrappers.isEmpty()) { return null; } + // Only return registry address when exactly one registry is registered for this service. + // With multiple registries we cannot determine which one handled the current invocation, + // so we return null and let the caller fall back to the provider IP address. + if (wrappers.size() != 1) { + return null; + } Object wrapper = wrappers.iterator().next(); Object urlObj = GET_REGISTRY_URL.invoke(wrapper); if (!(urlObj instanceof URL)) { From bcd862970b2dad01b993c965d437f731c85e9be7 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Tue, 7 Apr 2026 19:45:38 +0800 Subject: [PATCH 03/14] Refactor registry address capturing --- .../v2_7/DubboInstrumentationModule.java | 9 +- .../v2_7/RegistryAddressCaptureFilter.java | 26 -- .../RegistryCapturingClusterWrapperProxy.java | 35 ++ .../services/org.apache.dubbo.rpc.Filter | 1 - .../org.apache.dubbo.rpc.cluster.Cluster | 1 + .../v2_7/RegistryAddressCaptureFilter.java | 32 -- .../v2_7/internal/DubboRegistryUtil.java | 102 +----- .../RegistryCapturingClusterWrapper.java | 117 +++++++ .../internal/RegistryCapturingInvoker.java | 59 ++++ .../services/org.apache.dubbo.rpc.Filter | 1 - .../org.apache.dubbo.rpc.cluster.Cluster | 1 + .../v2_7/DubboRegistryUtilTest.java | 301 ------------------ 12 files changed, 236 insertions(+), 449 deletions(-) delete mode 100644 instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java create mode 100644 instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java create mode 100644 instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster delete mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster delete mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java index 94359a254972..dce46b7b9985 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboInstrumentationModule.java @@ -33,6 +33,9 @@ public void registerHelperResources(HelperResourceBuilder helperResourceBuilder) helperResourceBuilder.register( "META-INF/services/org.apache.dubbo.rpc.Filter", "apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.Filter"); + helperResourceBuilder.register( + "META-INF/services/org.apache.dubbo.rpc.cluster.Cluster", + "apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster"); } @Override @@ -44,15 +47,15 @@ public ElementMatcher.Junction classLoaderMatcher() { public void injectClasses(ClassInjector injector) { injector .proxyBuilder( - "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter") + "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter") .inject(InjectionMode.CLASS_ONLY); injector .proxyBuilder( - "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter") + "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryServerFilter") .inject(InjectionMode.CLASS_ONLY); injector .proxyBuilder( - "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryServerFilter") + "io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.RegistryCapturingClusterWrapperProxy") .inject(InjectionMode.CLASS_ONLY); } diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java deleted file mode 100644 index 1379807b2502..000000000000 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7; - -import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.rpc.Filter; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; - -@Activate( - group = {"consumer"}, - order = -10001) -public final class RegistryAddressCaptureFilter implements Filter { - - private final Filter delegate = - new io.opentelemetry.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter(); - - @Override - public Result invoke(Invoker invoker, Invocation invocation) { - return delegate.invoke(invoker, invocation); - } -} diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java new file mode 100644 index 000000000000..1b7f68a73140 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7; + +import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.RegistryCapturingClusterWrapper; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.cluster.Cluster; +import org.apache.dubbo.rpc.cluster.Directory; + +/** + * Javaagent proxy for {@link RegistryCapturingClusterWrapper}. Dubbo SPI discovers this class by + * name, so it must live in the javaagent module (which is not shaded) to keep a stable class name. + */ +public final class RegistryCapturingClusterWrapperProxy implements Cluster { + + private final RegistryCapturingClusterWrapper delegate; + + @SuppressWarnings("unused") + public RegistryCapturingClusterWrapperProxy(Cluster cluster) { + this.delegate = new RegistryCapturingClusterWrapper(cluster); + } + + @Override + public Invoker join(Directory directory) { + return delegate.join(directory); + } + + @SuppressWarnings("unused") + public Invoker join(Directory directory, boolean buildFilterChain) { + return delegate.join(directory, buildFilterChain); + } +} diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.Filter b/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.Filter index 8df6966f4758..f76248783a71 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.Filter +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.Filter @@ -1,3 +1,2 @@ -io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.OpenTelemetryServerFilter diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster b/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster new file mode 100644 index 000000000000..76d5f86920ef --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/resources/apache-dubbo-2.7/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster @@ -0,0 +1 @@ +io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7.RegistryCapturingClusterWrapperProxy diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java deleted file mode 100644 index 7126625be994..000000000000 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/RegistryAddressCaptureFilter.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.apachedubbo.v2_7; - -import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil; -import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.rpc.Filter; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcInvocation; - -@Activate( - group = {"consumer"}, - order = -10001) -public final class RegistryAddressCaptureFilter implements Filter { - - @Override - public Result invoke(Invoker invoker, Invocation invocation) { - if (invocation instanceof RpcInvocation) { - DubboRegistryUtil.captureRegistryAddress((RpcInvocation) invocation); - } - try { - return invoker.invoke(invocation); - } finally { - DubboRegistryUtil.clearCapturedRegistryAddress(); - } - } -} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index 8fccf09256a9..f29a97dd4f1c 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -11,7 +11,6 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.Optional; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import org.apache.dubbo.common.URL; @@ -37,22 +36,11 @@ public final class DubboRegistryUtil { private static final ThreadLocal CAPTURED_REGISTRY_ADDRESS = new ThreadLocal<>(); /** - * Called by RegistryAddressCaptureFilter (order -10001) before ConsumerContextFilter overwrites - * invocation.getInvoker(). In Dubbo 2.7.5+ and 3.x the invoker at this point is the cluster - * invoker with getDirectory(). + * Used by {@link RegistryCapturingInvoker} while the cluster delegate runs (including into + * consumer protocol filters). */ - public static void captureRegistryAddress(RpcInvocation invocation) { - Invoker invoker = invocation.getInvoker(); - if (invoker == null) { - return; - } - Directory directory = getDirectory(invoker); - if (directory != null) { - String address = extractRegistryAddressFromDirectory(directory); - if (address != null) { - CAPTURED_REGISTRY_ADDRESS.set(address); - } - } + static void pushCapturedRegistryAddress(String address) { + CAPTURED_REGISTRY_ADDRESS.set(address); } public static void clearCapturedRegistryAddress() { @@ -61,7 +49,6 @@ public static void clearCapturedRegistryAddress() { @Nullable public static String extractRegistryAddress(RpcInvocation invocation) { - // 1. Check ThreadLocal (set by RegistryAddressCaptureFilter for Dubbo 2.7.5+ and 3.x) String captured = CAPTURED_REGISTRY_ADDRESS.get(); if (captured != null) { return captured; @@ -72,17 +59,26 @@ public static String extractRegistryAddress(RpcInvocation invocation) { return null; } - // 2. Try invoker reflection (Dubbo 3.x fallback if capture filter didn't run) Directory directory = getDirectory(invoker); if (directory != null) { - String address = extractRegistryAddressFromDirectory(directory); + String address = tryExtractRegistryAddressFromDirectory(directory); if (address != null) { return address; } } - // 3. Try ProviderConsumerRegTable (Dubbo 2.7.0-2.7.4) - return RegTableLookup.extractFromRegTable(invoker.getUrl()); + return null; + } + + /** + * Resolves {@code protocol://host:port} from a registry-backed directory (for example {@code + * RegistryDirectory}), using {@code getRegistry()} when present and otherwise the {@code registry} + * field. Called once per consumer refer when {@link RegistryCapturingClusterWrapper} wraps the + * cluster invoker. + */ + @Nullable + public static String tryExtractRegistryAddressFromDirectory(Directory directory) { + return extractRegistryAddressFromDirectory(directory); } public static String buildServiceTarget(URL url) { @@ -206,69 +202,5 @@ private static MethodHandle resolveField(Class clazz, String name) { return null; } - /** - * Lazily-initialized holder for ProviderConsumerRegTable access. This class only exists in Dubbo - * 2.7.0-2.7.4; all reflection is wrapped to fail gracefully on Dubbo 2.7.5+ and 3.x. - */ - private static class RegTableLookup { - private static final MethodHandle CONSUMER_INVOKERS_GETTER; - private static final MethodHandle GET_REGISTRY_URL; - - static { - MethodHandle consumerInvokersGetter = null; - MethodHandle getRegistryUrl = null; - try { - Class tableClass = - Class.forName("org.apache.dubbo.registry.support.ProviderConsumerRegTable"); - Field f = tableClass.getField("consumerInvokers"); - consumerInvokersGetter = LOOKUP.unreflectGetter(f); - - Class wrapperClass = - Class.forName("org.apache.dubbo.registry.support.ConsumerInvokerWrapper"); - Method m = wrapperClass.getMethod("getRegistryUrl"); - getRegistryUrl = LOOKUP.unreflect(m); - } catch (Throwable ignored) { - // ProviderConsumerRegTable not available (Dubbo 2.7.5+ or 3.x) - } - CONSUMER_INVOKERS_GETTER = consumerInvokersGetter; - GET_REGISTRY_URL = getRegistryUrl; - } - - @Nullable - @SuppressWarnings("unchecked") // ConcurrentHashMap generic cast from reflective access - static String extractFromRegTable(URL invokerUrl) { - if (CONSUMER_INVOKERS_GETTER == null || GET_REGISTRY_URL == null) { - return null; - } - try { - String serviceKey = invokerUrl.getServiceKey(); - if (serviceKey == null || serviceKey.isEmpty()) { - return null; - } - ConcurrentHashMap> table = - (ConcurrentHashMap>) CONSUMER_INVOKERS_GETTER.invoke(); - Set wrappers = table.get(serviceKey); - if (wrappers == null || wrappers.isEmpty()) { - return null; - } - // Only return registry address when exactly one registry is registered for this service. - // With multiple registries we cannot determine which one handled the current invocation, - // so we return null and let the caller fall back to the provider IP address. - if (wrappers.size() != 1) { - return null; - } - Object wrapper = wrappers.iterator().next(); - Object urlObj = GET_REGISTRY_URL.invoke(wrapper); - if (!(urlObj instanceof URL)) { - return null; - } - URL registryUrl = (URL) urlObj; - return registryUrl.getProtocol() + "://" + registryUrl.getAddress(); - } catch (Throwable t) { - return null; - } - } - } - private DubboRegistryUtil() {} } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java new file mode 100644 index 000000000000..c4cc0eb7236c --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -0,0 +1,117 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Method; +import javax.annotation.Nullable; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.cluster.Cluster; +import org.apache.dubbo.rpc.cluster.Directory; + +/** + * Dubbo {@link Cluster} SPI wrapper that records the registry URL for each registry-backed {@link + * Directory} when the cluster invoker is entered, so consumer {@link org.apache.dubbo.rpc.Filter} + * implementations can read it from {@link DubboRegistryUtil}. + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. + */ +public final class RegistryCapturingClusterWrapper implements Cluster { + + private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + + /** Dubbo 3.0.4+: join(Directory, boolean). Absent on 2.7 and 3.0.0-3.0.3. */ + @Nullable private static final MethodHandle JOIN_TWO_ARG; + + /** Dubbo 2.7 and 3.0.0-3.0.3: join(Directory). Absent on 3.0.4+. */ + @Nullable private static final MethodHandle JOIN_ONE_ARG; + + static { + MethodHandle two = null; + MethodHandle one = null; + try { + Method m = + Cluster.class.getMethod("join", Directory.class, boolean.class); + m.setAccessible(true); + two = LOOKUP.unreflect(m); + } catch (ReflectiveOperationException ignored) { + // Dubbo 2.7 / 3.0.0-3.0.3 + } + try { + Method m = Cluster.class.getMethod("join", Directory.class); + m.setAccessible(true); + one = LOOKUP.unreflect(m); + } catch (ReflectiveOperationException ignored) { + // Dubbo 3.0.4+ + } + JOIN_TWO_ARG = two; + JOIN_ONE_ARG = one; + } + + private final Cluster cluster; + + /** SPI wrapper constructor — must accept the delegate {@link Cluster}. */ + @SuppressWarnings("unused") // Used by Dubbo ExtensionLoader via reflection + public RegistryCapturingClusterWrapper(Cluster cluster) { + this.cluster = cluster; + } + + @Override + public Invoker join(Directory directory) { + return wrapIfNeeded(directory, delegateJoin(cluster, directory, true)); + } + + /** + * Dubbo 3.0.4+ entry point. Not an {@code @Override} when compiling against Dubbo 2.7, but + * required for {@link Cluster} at runtime on 3.0.4+. + */ + @SuppressWarnings("unused") + public Invoker join(Directory directory, boolean buildFilterChain) { + return wrapIfNeeded(directory, delegateJoin(cluster, directory, buildFilterChain)); + } + + /** + * {@link MethodHandle#invoke} is untyped; the returned invoker matches the generic {@code T} from + * the {@code directory} argument and delegate {@link Cluster#join}. + */ + @SuppressWarnings("unchecked") + private static Invoker delegateJoin( + Cluster cluster, Directory directory, boolean buildFilterChain) { + try { + if (JOIN_TWO_ARG != null) { + return (Invoker) JOIN_TWO_ARG.invoke(cluster, directory, buildFilterChain); + } + if (JOIN_ONE_ARG != null) { + return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); + } + } catch (RpcException e) { + throw e; + } catch (Throwable t) { + throw new RpcException(t.getMessage(), t); + } + throw new RpcException("No join(Directory) or join(Directory,boolean) on Cluster"); + } + + private static Invoker wrapIfNeeded(Directory directory, Invoker invoker) { + if (isStaticDirectory(directory)) { + return invoker; + } + String registryAddress = DubboRegistryUtil.tryExtractRegistryAddressFromDirectory(directory); + if (registryAddress == null) { + return invoker; + } + return new RegistryCapturingInvoker<>(invoker, registryAddress); + } + + private static boolean isStaticDirectory(Directory directory) { + return directory != null + && "org.apache.dubbo.rpc.cluster.directory.StaticDirectory" + .equals(directory.getClass().getName()); + } +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java new file mode 100644 index 000000000000..d611e5d6d90b --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java @@ -0,0 +1,59 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; + +/** + * Wraps a cluster invoker to publish the consumer registry address for the current thread while the + * delegate chain runs (for example into the Dubbo consumer protocol filter chain). + * + *

This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +final class RegistryCapturingInvoker implements Invoker { + + private final Invoker delegate; + private final String registryAddress; + + RegistryCapturingInvoker(Invoker delegate, String registryAddress) { + this.delegate = delegate; + this.registryAddress = registryAddress; + } + + @Override + public Class getInterface() { + return delegate.getInterface(); + } + + @Override + public URL getUrl() { + return delegate.getUrl(); + } + + @Override + public boolean isAvailable() { + return delegate.isAvailable(); + } + + @Override + public void destroy() { + delegate.destroy(); + } + + @Override + public Result invoke(Invocation invocation) { + DubboRegistryUtil.pushCapturedRegistryAddress(registryAddress); + try { + return delegate.invoke(invocation); + } finally { + DubboRegistryUtil.clearCapturedRegistryAddress(); + } + } +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter index 9d88724e4bca..250516c0dc0b 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.Filter @@ -1,3 +1,2 @@ -io.opentelemetry.instrumentation.apachedubbo.v2_7.RegistryAddressCaptureFilter io.opentelemetry.instrumentation.apachedubbo.v2_7.OpenTelemetryClientFilter io.opentelemetry.instrumentation.apachedubbo.v2_7.OpenTelemetryServerFilter diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster new file mode 100644 index 000000000000..91645ea1aa83 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/resources/META-INF/services/org.apache.dubbo.rpc.cluster.Cluster @@ -0,0 +1 @@ +io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.RegistryCapturingClusterWrapper diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java deleted file mode 100644 index aabfaf44057f..000000000000 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryUtilTest.java +++ /dev/null @@ -1,301 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.apachedubbo.v2_7; - -import static java.util.Collections.emptyList; -import static java.util.Collections.singletonList; -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil; -import java.util.List; -import org.apache.dubbo.common.URL; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.cluster.Directory; -import org.apache.dubbo.rpc.cluster.directory.StaticDirectory; -import org.junit.jupiter.api.Test; - -@SuppressWarnings("deprecation") -class DubboRegistryUtilTest { - - private static final URL DUMMY_URL = - URL.valueOf("dubbo://192.168.1.100:20880/com.example.Service"); - - @Test - void extractRegistryAddress_returnsAddressForRegistryDirectory() { - FakeClusterInvoker invoker = new FakeClusterInvoker(new FakeRegistryDirectory(), DUMMY_URL); - RpcInvocation invocation = new RpcInvocation(); - invocation.setInvoker(invoker); - assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)) - .isEqualTo("nacos://127.0.0.1:8848"); - } - - @Test - void extractRegistryAddress_returnsNullForStaticDirectory() { - StubInvoker stub = new StubInvoker(DUMMY_URL); - StaticDirectory staticDir = new StaticDirectory<>(DUMMY_URL, singletonList(stub)); - FakeClusterInvoker invoker = new FakeClusterInvoker(staticDir, DUMMY_URL); - RpcInvocation invocation = new RpcInvocation(); - invocation.setInvoker(invoker); - assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); - } - - @Test - void extractRegistryAddress_returnsNullWhenNoInvoker() { - RpcInvocation invocation = new RpcInvocation(); - assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); - } - - @Test - void extractRegistryAddress_returnsNullWhenInvokerHasNoDirectory() { - RpcInvocation invocation = new RpcInvocation(); - invocation.setInvoker(new StubInvoker(DUMMY_URL)); - assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); - } - - @Test - void extractRegistryAddress_fieldFallback_returnsAddressForClusterInvokerWithFieldOnly() { - FieldOnlyClusterInvoker invoker = - new FieldOnlyClusterInvoker(new FieldOnlyRegistryDirectory(), DUMMY_URL); - RpcInvocation invocation = new RpcInvocation(); - invocation.setInvoker(invoker); - assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)) - .isEqualTo("zookeeper://10.0.0.1:2181"); - } - - @Test - void buildServiceTarget_interfaceOnly() { - URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService"); - assertThat(DubboRegistryUtil.buildServiceTarget(url)).isEqualTo("com.example.HelloService"); - } - - @Test - void buildServiceTarget_withVersion() { - URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService?version=2.0.0"); - assertThat(DubboRegistryUtil.buildServiceTarget(url)) - .isEqualTo("com.example.HelloService:2.0.0"); - } - - @Test - void buildServiceTarget_withVersionAndGroup() { - URL url = - URL.valueOf( - "dubbo://192.168.1.100:20880/com.example.HelloService?version=2.0.0&group=gray"); - assertThat(DubboRegistryUtil.buildServiceTarget(url)) - .isEqualTo("com.example.HelloService:2.0.0:gray"); - } - - @Test - void buildServiceTarget_withGroupOnly() { - URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService?group=gray"); - assertThat(DubboRegistryUtil.buildServiceTarget(url)) - .isEqualTo("com.example.HelloService::gray"); - } - - @Test - void buildServiceTarget_withEmptyVersionAndGroup() { - URL url = URL.valueOf("dubbo://192.168.1.100:20880/com.example.HelloService?version=&group="); - assertThat(DubboRegistryUtil.buildServiceTarget(url)).isEqualTo("com.example.HelloService"); - } - - @SuppressWarnings({"unused", "UnusedMethod", "MethodCanBeStatic", "EffectivelyPrivate"}) - private static class FakeClusterInvoker implements Invoker { - - private final Directory directory; - private final URL url; - - FakeClusterInvoker(Directory directory, URL url) { - this.directory = directory; - this.url = url; - } - - public Directory getDirectory() { - return directory; - } - - @Override - public Class getInterface() { - return Object.class; - } - - @Override - public Result invoke(Invocation invocation) { - throw new UnsupportedOperationException(); - } - - @Override - public URL getUrl() { - return url; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void destroy() {} - } - - private static class StubInvoker implements Invoker { - - private final URL url; - - StubInvoker(URL url) { - this.url = url; - } - - @Override - public Class getInterface() { - return Object.class; - } - - @Override - public Result invoke(Invocation invocation) { - throw new UnsupportedOperationException(); - } - - @Override - public URL getUrl() { - return url; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void destroy() {} - } - - @SuppressWarnings({ - "rawtypes", - "unchecked", - "EffectivelyPrivate", - "UnusedMethod", - "MethodCanBeStatic" - }) - private static class FakeRegistryDirectory implements Directory { - - public FakeRegistry getRegistry() { - return new FakeRegistry(); - } - - @Override - public Class getInterface() { - return Object.class; - } - - @Override - public List> list(Invocation invocation) { - return emptyList(); - } - - @Override - public List> getAllInvokers() { - return emptyList(); - } - - @Override - public URL getUrl() { - return URL.valueOf("dubbo://localhost:20880/com.example.Service"); - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void destroy() {} - } - - static class FakeRegistry { - @SuppressWarnings("unused") - public URL getUrl() { - return URL.valueOf("nacos://127.0.0.1:8848/org.apache.dubbo.registry.RegistryService"); - } - } - - @SuppressWarnings({"unused", "EffectivelyPrivate"}) - private static class FieldOnlyClusterInvoker implements Invoker { - - private final Directory directory; - private final URL url; - - FieldOnlyClusterInvoker(Directory directory, URL url) { - this.directory = directory; - this.url = url; - } - - @Override - public Class getInterface() { - return Object.class; - } - - @Override - public Result invoke(Invocation invocation) { - throw new UnsupportedOperationException(); - } - - @Override - public URL getUrl() { - return url; - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void destroy() {} - } - - @SuppressWarnings({"rawtypes", "unchecked", "unused", "EffectivelyPrivate"}) - private static class FieldOnlyRegistryDirectory implements Directory { - - private final FakeZkRegistry registry = new FakeZkRegistry(); - - @Override - public Class getInterface() { - return Object.class; - } - - @Override - public List> list(Invocation invocation) { - return emptyList(); - } - - @Override - public List> getAllInvokers() { - return emptyList(); - } - - @Override - public URL getUrl() { - return URL.valueOf("dubbo://localhost:20880/com.example.Service"); - } - - @Override - public boolean isAvailable() { - return true; - } - - @Override - public void destroy() {} - } - - static class FakeZkRegistry { - @SuppressWarnings("unused") - public URL getUrl() { - return URL.valueOf("zookeeper://10.0.0.1:2181/org.apache.dubbo.registry.RegistryService"); - } - } -} From e974692618cd48941780a3c71accfe5119efec9b Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Tue, 7 Apr 2026 22:11:51 +0800 Subject: [PATCH 04/14] Add integration tests for Dubbo registry mode and enhance registry address extraction --- .../v2_7/DubboAgentRegistryTest.java | 22 ++ .../v2_7/internal/DubboRegistryUtil.java | 6 +- .../RegistryCapturingClusterWrapper.java | 3 +- .../internal/RegistryCapturingInvoker.java | 4 +- .../apachedubbo/v2_7/DubboRegistryTest.java | 21 ++ .../v2_7/internal/DubboRegistryUtilTest.java | 133 +++++++++++ .../RegistryCapturingClusterWrapperTest.java | 102 +++++++++ .../apache-dubbo-2.7/testing/build.gradle.kts | 4 + .../v2_7/AbstractDubboRegistryTest.java | 212 ++++++++++++++++++ 9 files changed, 500 insertions(+), 7 deletions(-) create mode 100644 instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java create mode 100644 instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java create mode 100644 instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java b/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java new file mode 100644 index 000000000000..1079823d57c2 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java @@ -0,0 +1,22 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.apachedubbo.v2_7; + +import io.opentelemetry.instrumentation.apachedubbo.v2_7.AbstractDubboRegistryTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class DubboAgentRegistryTest extends AbstractDubboRegistryTest { + + @RegisterExtension + static final InstrumentationExtension testing = AgentInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index f29a97dd4f1c..0cdbbd7ccff0 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -72,9 +72,9 @@ public static String extractRegistryAddress(RpcInvocation invocation) { /** * Resolves {@code protocol://host:port} from a registry-backed directory (for example {@code - * RegistryDirectory}), using {@code getRegistry()} when present and otherwise the {@code registry} - * field. Called once per consumer refer when {@link RegistryCapturingClusterWrapper} wraps the - * cluster invoker. + * RegistryDirectory}), using {@code getRegistry()} when present and otherwise the {@code + * registry} field. Called once per consumer refer when {@link RegistryCapturingClusterWrapper} + * wraps the cluster invoker. */ @Nullable public static String tryExtractRegistryAddressFromDirectory(Directory directory) { diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java index c4cc0eb7236c..5c96e45fa30d 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -36,8 +36,7 @@ public final class RegistryCapturingClusterWrapper implements Cluster { MethodHandle two = null; MethodHandle one = null; try { - Method m = - Cluster.class.getMethod("join", Directory.class, boolean.class); + Method m = Cluster.class.getMethod("join", Directory.class, boolean.class); m.setAccessible(true); two = LOOKUP.unreflect(m); } catch (ReflectiveOperationException ignored) { diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java index d611e5d6d90b..d96fcac3a8f5 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java @@ -14,8 +14,8 @@ * Wraps a cluster invoker to publish the consumer registry address for the current thread while the * delegate chain runs (for example into the Dubbo consumer protocol filter chain). * - *

This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. + *

This class is internal and is hence not for public use. Its APIs are unstable and can change + * at any time. */ final class RegistryCapturingInvoker implements Invoker { diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java new file mode 100644 index 000000000000..b7eba1b367a4 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7; + +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class DubboRegistryTest extends AbstractDubboRegistryTest { + + @RegisterExtension + static final InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + protected InstrumentationExtension testing() { + return testing; + } +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java new file mode 100644 index 000000000000..1dc777817680 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java @@ -0,0 +1,133 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Field; +import java.util.stream.Stream; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.cluster.Directory; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@SuppressWarnings("deprecation") +class DubboRegistryUtilTest { + + private static final URL DUMMY_URL = + URL.valueOf("dubbo://192.168.1.100:20880/com.example.Service"); + + @AfterEach + void tearDown() { + DubboRegistryUtil.clearCapturedRegistryAddress(); + } + + @ParameterizedTest + @MethodSource("serviceTargetProvider") + void buildServiceTarget(String urlString, String expected) { + assertThat(DubboRegistryUtil.buildServiceTarget(URL.valueOf(urlString))).isEqualTo(expected); + } + + static Stream serviceTargetProvider() { + return Stream.of( + Arguments.of( + "dubbo://192.168.1.100:20880/com.example.HelloService", "com.example.HelloService"), + Arguments.of( + "dubbo://192.168.1.100:20880/com.example.HelloService?version=2.0.0", + "com.example.HelloService:2.0.0"), + Arguments.of( + "dubbo://192.168.1.100:20880/com.example.HelloService?version=2.0.0&group=gray", + "com.example.HelloService:2.0.0:gray"), + Arguments.of( + "dubbo://192.168.1.100:20880/com.example.HelloService?group=gray", + "com.example.HelloService::gray"), + Arguments.of( + "dubbo://192.168.1.100:20880/com.example.HelloService?version=&group=", + "com.example.HelloService"), + Arguments.of("dubbo://192.168.1.100:20880/", ""), + Arguments.of("dubbo://192.168.1.100:20880", "")); + } + + @Test + void extractRegistryAddressReturnsNullWhenNoInvoker() { + RpcInvocation invocation = new RpcInvocation(); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)).isNull(); + } + + /** Tests the field-based reflection fallback when no {@code getDirectory()} method exists. */ + @Test + void extractRegistryAddressFieldFallback() throws Exception { + Directory dir = mockDirectoryWithRegistry(new FakeZkRegistry()); + FieldOnlyClusterInvoker invoker = new FieldOnlyClusterInvoker(dir, DUMMY_URL); + RpcInvocation invocation = new RpcInvocation(); + invocation.setInvoker(invoker); + assertThat(DubboRegistryUtil.extractRegistryAddress(invocation)) + .isEqualTo("zookeeper://10.0.0.1:2181"); + } + + @SuppressWarnings("all") + abstract static class MockableRegistryDirectory implements Directory { + Object registry; + } + + @SuppressWarnings("unchecked") + private static Directory mockDirectoryWithRegistry(Object registry) throws Exception { + MockableRegistryDirectory dir = mock(MockableRegistryDirectory.class); + Field f = MockableRegistryDirectory.class.getDeclaredField("registry"); + f.setAccessible(true); + f.set(dir, registry); + return dir; + } + + @SuppressWarnings({"unused", "EffectivelyPrivate"}) + private static class FieldOnlyClusterInvoker implements Invoker { + private final Directory directory; + private final URL url; + + FieldOnlyClusterInvoker(Directory directory, URL url) { + this.directory = directory; + this.url = url; + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public Result invoke(Invocation invocation) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + static class FakeZkRegistry { + @SuppressWarnings("unused") + public URL getUrl() { + return URL.valueOf("zookeeper://10.0.0.1:2181/org.apache.dubbo.registry.RegistryService"); + } + } +} diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java new file mode 100644 index 000000000000..ba3218dad7b6 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.same; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.cluster.Cluster; +import org.apache.dubbo.rpc.cluster.directory.StaticDirectory; +import org.junit.jupiter.api.Test; + +class RegistryCapturingClusterWrapperTest { + + private static final URL DUMMY_URL = + URL.valueOf("dubbo://192.168.1.100:20880/com.example.Service"); + + @Test + @SuppressWarnings("unchecked") + void joinDoesNotWrapStaticDirectory() { + Cluster inner = mock(Cluster.class); + RegistryCapturingClusterWrapper wrapper = new RegistryCapturingClusterWrapper(inner); + Invoker innerInvoker = new NoopInvoker(DUMMY_URL); + StubInvoker stub = new StubInvoker(DUMMY_URL); + StaticDirectory staticDir = new StaticDirectory<>(singletonList(stub)); + when(inner.join(same(staticDir))).thenReturn(innerInvoker); + + Invoker out = wrapper.join(staticDir); + assertThat(out).isSameAs(innerInvoker); + } + + private static class NoopInvoker implements Invoker { + private final URL url; + + NoopInvoker(URL url) { + this.url = url; + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public Result invoke(Invocation invocation) { + return null; + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } + + private static class StubInvoker implements Invoker { + private final URL url; + + StubInvoker(URL url) { + this.url = url; + } + + @Override + public Class getInterface() { + return Object.class; + } + + @Override + public Result invoke(Invocation invocation) { + throw new UnsupportedOperationException(); + } + + @Override + public URL getUrl() { + return url; + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public void destroy() {} + } +} diff --git a/instrumentation/apache-dubbo-2.7/testing/build.gradle.kts b/instrumentation/apache-dubbo-2.7/testing/build.gradle.kts index b83300a25234..def329fc28ec 100644 --- a/instrumentation/apache-dubbo-2.7/testing/build.gradle.kts +++ b/instrumentation/apache-dubbo-2.7/testing/build.gradle.kts @@ -10,6 +10,10 @@ dependencies { api("org.apache.dubbo:dubbo:$apacheDubboVersion") api("org.apache.dubbo:dubbo-config-api:$apacheDubboVersion") + api("org.apache.dubbo:dubbo-registry-zookeeper:$apacheDubboVersion") + api("org.apache.curator:curator-test:5.9.0") + api("org.apache.curator:curator-recipes:5.9.0") + implementation("javax.annotation:javax.annotation-api:1.3.2") implementation("io.opentelemetry:opentelemetry-api") diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java new file mode 100644 index 000000000000..2d501e78ea31 --- /dev/null +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java @@ -0,0 +1,212 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.apachedubbo.v2_7; + +import static io.opentelemetry.instrumentation.apachedubbo.v2_7.AbstractDubboTest.assertLatestDeps; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv; +import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; +import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_ADDRESS; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_PEER_PORT; +import static io.opentelemetry.semconv.NetworkAttributes.NETWORK_TYPE; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_ADDRESS; +import static io.opentelemetry.semconv.ServerAttributes.SERVER_PORT; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_METHOD; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SERVICE; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM; +import static io.opentelemetry.semconv.incubating.RpcIncubatingAttributes.RPC_SYSTEM_NAME; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService; +import io.opentelemetry.instrumentation.apachedubbo.v2_7.impl.HelloServiceImpl; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import java.lang.reflect.Field; +import java.net.InetAddress; +import org.apache.curator.test.TestingServer; +import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; +import org.apache.dubbo.config.ReferenceConfig; +import org.apache.dubbo.config.RegistryConfig; +import org.apache.dubbo.config.ServiceConfig; +import org.apache.dubbo.config.bootstrap.DubboBootstrap; +import org.apache.dubbo.rpc.service.GenericService; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +/** + * Integration test that verifies the registry-mode end-to-end flow: provider registers to + * ZooKeeper, consumer discovers via ZooKeeper, and the {@code SERVER_ADDRESS} span attribute + * contains the registry address (with service interface, version, and group) instead of the + * provider host. + */ +@SuppressWarnings("deprecation") // using deprecated semconv +public abstract class AbstractDubboRegistryTest { + + private static final String SERVICE_VERSION = "1.0.0"; + private static final String SERVICE_GROUP = "testGroup"; + + private static TestingServer zkServer; + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + protected abstract InstrumentationExtension testing(); + + @BeforeAll + static void setUp() throws Exception { + zkServer = new TestingServer(); + zkServer.start(); + + System.setProperty("dubbo.application.qos-enable", "false"); + Field field = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); + field.setAccessible(true); + field.set(null, InetAddress.getLoopbackAddress()); + } + + @AfterAll + static void tearDown() throws Exception { + System.clearProperty("dubbo.application.qos-enable"); + if (zkServer != null) { + zkServer.close(); + } + } + + static String zkAddress() { + return "zookeeper://127.0.0.1:" + zkServer.getPort(); + } + + private static RegistryConfig newZkRegistryConfig(String zkAddr) { + RegistryConfig config = new RegistryConfig(); + config.setAddress(zkAddr); + config.setUseAsConfigCenter(false); + config.setUseAsMetadataCenter(false); + return config; + } + + @Test + void testRegistryModeServerAddress() throws ReflectiveOperationException { + int port = PortUtils.findOpenPort(); + String zkAddr = zkAddress(); + + ProtocolConfig protocolConfig = new ProtocolConfig(); + protocolConfig.setPort(port); + + ServiceConfig service = new ServiceConfig<>(); + service.setInterface(HelloService.class); + service.setRef(new HelloServiceImpl()); + service.setVersion(SERVICE_VERSION); + service.setGroup(SERVICE_GROUP); + + DubboBootstrap providerBootstrap = DubboTestUtil.newDubboBootstrap(); + cleanup.deferCleanup(providerBootstrap::destroy); + providerBootstrap + .application(new ApplicationConfig("dubbo-registry-test-provider")) + .registry(newZkRegistryConfig(zkAddr)) + .service(service) + .protocol(protocolConfig) + .start(); + + // --- consumer: discover from ZooKeeper (must disable injvm to avoid same-JVM shortcut) --- + ReferenceConfig referenceConfig = new ReferenceConfig<>(); + referenceConfig.setInterface(HelloService.class); + referenceConfig.setGeneric("true"); + referenceConfig.setVersion(SERVICE_VERSION); + referenceConfig.setGroup(SERVICE_GROUP); + referenceConfig.setTimeout(30000); + referenceConfig.setInjvm(false); + + ProtocolConfig consumerProtocol = new ProtocolConfig(); + consumerProtocol.setRegister(false); + + DubboBootstrap consumerBootstrap = DubboTestUtil.newDubboBootstrap(); + cleanup.deferCleanup(consumerBootstrap::destroy); + consumerBootstrap + .application(new ApplicationConfig("dubbo-registry-test-consumer")) + .registry(newZkRegistryConfig(zkAddr)) + .reference(referenceConfig) + .protocol(consumerProtocol) + .start(); + + @SuppressWarnings({"rawtypes", "unchecked"}) + ReferenceConfig reference = (ReferenceConfig) referenceConfig; + GenericService genericService = reference.get(); + + Object response = + runWithSpan( + "parent", + () -> + genericService.$invoke( + "hello", new String[] {String.class.getName()}, new Object[] {"hello"})); + + assertThat(response).isEqualTo("hello"); + + // In registry mode, SERVER_ADDRESS = "registryProtocol://host:port/interface:version:group" + // and SERVER_PORT is absent (null). + // See https://github.com/open-telemetry/semantic-conventions/pull/3317 + String expectedServiceTarget = + HelloService.class.getName() + ":" + SERVICE_VERSION + ":" + SERVICE_GROUP; + String expectedServerAddress = zkAddr + "/" + expectedServiceTarget; + + testing() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(), + span -> + span.hasName("org.apache.dubbo.rpc.service.GenericService/$invoke") + .hasKind(SpanKind.CLIENT) + .hasParent(trace.getSpan(0)) + .hasAttributesSatisfyingExactly( + equalTo(RPC_SYSTEM, emitOldRpcSemconv() ? "apache_dubbo" : null), + equalTo(RPC_SYSTEM_NAME, emitStableRpcSemconv() ? "dubbo" : null), + equalTo( + RPC_SERVICE, + emitOldRpcSemconv() + ? "org.apache.dubbo.rpc.service.GenericService" + : null), + equalTo( + RPC_METHOD, + emitStableRpcSemconv() + ? "org.apache.dubbo.rpc.service.GenericService/$invoke" + : "$invoke"), + equalTo(SERVER_ADDRESS, expectedServerAddress), + equalTo(SERVER_PORT, null), + satisfies( + NETWORK_PEER_ADDRESS, + k -> assertLatestDeps(k, a -> a.isInstanceOf(String.class))), + satisfies( + NETWORK_PEER_PORT, + k -> assertLatestDeps(k, a -> a.isInstanceOf(Long.class))), + satisfies(NETWORK_TYPE, AbstractDubboTest::assertNetworkType)), + span -> + span.hasName( + "io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService/hello") + .hasKind(SpanKind.SERVER) + .hasParent(trace.getSpan(1)) + .hasAttributesSatisfying( + equalTo(RPC_SYSTEM, emitOldRpcSemconv() ? "apache_dubbo" : null), + equalTo(RPC_SYSTEM_NAME, emitStableRpcSemconv() ? "dubbo" : null), + equalTo( + RPC_SERVICE, + emitOldRpcSemconv() + ? "io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService" + : null), + equalTo( + RPC_METHOD, + emitStableRpcSemconv() + ? "io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService/hello" + : "hello"), + satisfies(NETWORK_PEER_ADDRESS, k -> k.isInstanceOf(String.class)), + satisfies(NETWORK_PEER_PORT, k -> k.isInstanceOf(Long.class))))); + } +} From 2646ed496fe6ca90e280246dfa41dca5f142eb1e Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Tue, 7 Apr 2026 23:14:25 +0800 Subject: [PATCH 05/14] Fix failing test --- .../RegistryCapturingClusterWrapperTest.java | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java index ba3218dad7b6..8eaae805598c 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java @@ -7,15 +7,13 @@ import static java.util.Collections.singletonList; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.same; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; import org.apache.dubbo.common.URL; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.cluster.Cluster; +import org.apache.dubbo.rpc.cluster.Directory; import org.apache.dubbo.rpc.cluster.directory.StaticDirectory; import org.junit.jupiter.api.Test; @@ -27,17 +25,44 @@ class RegistryCapturingClusterWrapperTest { @Test @SuppressWarnings("unchecked") void joinDoesNotWrapStaticDirectory() { - Cluster inner = mock(Cluster.class); - RegistryCapturingClusterWrapper wrapper = new RegistryCapturingClusterWrapper(inner); Invoker innerInvoker = new NoopInvoker(DUMMY_URL); + RegistryCapturingClusterWrapper wrapper = + new RegistryCapturingClusterWrapper(new FakeCluster(innerInvoker)); StubInvoker stub = new StubInvoker(DUMMY_URL); StaticDirectory staticDir = new StaticDirectory<>(singletonList(stub)); - when(inner.join(same(staticDir))).thenReturn(innerInvoker); Invoker out = wrapper.join(staticDir); assertThat(out).isSameAs(innerInvoker); } + /** + * Fake {@link Cluster} that provides both the Dubbo 2.7 {@code join(Directory)} and 3.0.4+ {@code + * join(Directory, boolean)} signatures, following the same pattern as {@link + * RegistryCapturingClusterWrapper}. + */ + @SuppressWarnings("unchecked") + private static class FakeCluster implements Cluster { + private final Invoker invoker; + + FakeCluster(Invoker invoker) { + this.invoker = invoker; + } + + // Dubbo 2.7 signature + // @Override — present in 2.7, removed in 3.0.4+ + @SuppressWarnings({"MissingOverride", "UnusedMethod", "UnusedVariable", "EffectivelyPrivate"}) + public Invoker join(Directory directory) { + return (Invoker) invoker; + } + + // Dubbo 3.0.4+ signature + // @Override — present in 3.0.4+, absent in 2.7 + @SuppressWarnings({"MissingOverride", "UnusedMethod", "UnusedVariable", "EffectivelyPrivate"}) + public Invoker join(Directory directory, boolean buildFilterChain) { + return (Invoker) invoker; + } + } + private static class NoopInvoker implements Invoker { private final URL url; From e2c19d0f9e9fcb437a6dc717b69b32935d54cffc Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Wed, 15 Apr 2026 21:08:41 +0800 Subject: [PATCH 06/14] Optimize code --- .../v2_7/RegistryCapturingClusterWrapperProxy.java | 4 ---- .../internal/DubboClientNetworkAttributesGetter.java | 9 ++++++--- .../apachedubbo/v2_7/internal/DubboRegistryUtil.java | 5 +++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java index 1b7f68a73140..a368826c8dbc 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/RegistryCapturingClusterWrapperProxy.java @@ -10,10 +10,6 @@ import org.apache.dubbo.rpc.cluster.Cluster; import org.apache.dubbo.rpc.cluster.Directory; -/** - * Javaagent proxy for {@link RegistryCapturingClusterWrapper}. Dubbo SPI discovers this class by - * name, so it must live in the javaagent module (which is not shaded) to keep a stable class name. - */ public final class RegistryCapturingClusterWrapperProxy implements Cluster { private final RegistryCapturingClusterWrapper delegate; diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java index 391eb5f5f438..0cc355e1b640 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java @@ -5,6 +5,9 @@ package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; +import static io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil.buildServiceTarget; +import static io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil.extractRegistryAddress; + import io.opentelemetry.instrumentation.apachedubbo.v2_7.DubboRequest; import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; import io.opentelemetry.instrumentation.api.semconv.network.ServerAttributesGetter; @@ -22,9 +25,9 @@ public final class DubboClientNetworkAttributesGetter @Nullable @Override public String getServerAddress(DubboRequest request) { - String registryAddress = DubboRegistryUtil.extractRegistryAddress(request.invocation()); + String registryAddress = extractRegistryAddress(request.invocation()); if (registryAddress != null) { - return registryAddress + "/" + DubboRegistryUtil.buildServiceTarget(request.url()); + return registryAddress + "/" + buildServiceTarget(request.url()); } return request.url().getHost(); } @@ -32,7 +35,7 @@ public String getServerAddress(DubboRequest request) { @Nullable @Override public Integer getServerPort(DubboRequest request) { - if (DubboRegistryUtil.extractRegistryAddress(request.invocation()) != null) { + if (extractRegistryAddress(request.invocation()) != null) { return null; } return request.url().getPort(); diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index 0cdbbd7ccff0..732fdff31db3 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -184,8 +184,9 @@ private static MethodHandle resolveMethod(Class clazz, String name) { m.setAccessible(true); return LOOKUP.unreflect(m); } catch (NoSuchMethodException | IllegalAccessException e) { - return null; + // ignore } + return null; } @Nullable @@ -196,7 +197,7 @@ private static MethodHandle resolveField(Class clazz, String name) { f.setAccessible(true); return LOOKUP.unreflectGetter(f); } catch (NoSuchFieldException | IllegalAccessException e) { - // continue up the hierarchy + // ignore } } return null; From 379318f3b79f5038bf7864fff0bfba8a4e915dc8 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Tue, 21 Apr 2026 17:54:15 +0800 Subject: [PATCH 07/14] Address review comments --- .../apachedubbo/v2_7/DubboRequest.java | 8 +++- .../DubboClientNetworkAttributesGetter.java | 5 +-- .../v2_7/internal/DubboRegistryUtil.java | 38 ++++++++++--------- .../internal/RegistryCapturingInvoker.java | 4 +- .../v2_7/internal/DubboRegistryUtilTest.java | 4 +- .../v2_7/AbstractDubboRegistryTest.java | 13 ++++--- 6 files changed, 41 insertions(+), 31 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java index a80544336d82..d357cc808b84 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java @@ -6,6 +6,7 @@ package io.opentelemetry.instrumentation.apachedubbo.v2_7; import com.google.auto.value.AutoValue; +import io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil; import java.net.InetSocketAddress; import javax.annotation.Nullable; import org.apache.dubbo.common.URL; @@ -26,7 +27,12 @@ static DubboRequest create(RpcInvocation invocation, RpcContext context) { context.getLocalAddress()); } - public abstract RpcInvocation invocation(); + abstract RpcInvocation invocation(); + + @Nullable + public String registryAddress() { + return DubboRegistryUtil.extractRegistryAddress(invocation()); + } public abstract RpcContext context(); diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java index 0cc355e1b640..4931ab542027 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboClientNetworkAttributesGetter.java @@ -6,7 +6,6 @@ package io.opentelemetry.instrumentation.apachedubbo.v2_7.internal; import static io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil.buildServiceTarget; -import static io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.DubboRegistryUtil.extractRegistryAddress; import io.opentelemetry.instrumentation.apachedubbo.v2_7.DubboRequest; import io.opentelemetry.instrumentation.api.semconv.network.NetworkAttributesGetter; @@ -25,7 +24,7 @@ public final class DubboClientNetworkAttributesGetter @Nullable @Override public String getServerAddress(DubboRequest request) { - String registryAddress = extractRegistryAddress(request.invocation()); + String registryAddress = request.registryAddress(); if (registryAddress != null) { return registryAddress + "/" + buildServiceTarget(request.url()); } @@ -35,7 +34,7 @@ public String getServerAddress(DubboRequest request) { @Nullable @Override public Integer getServerPort(DubboRequest request) { - if (extractRegistryAddress(request.invocation()) != null) { + if (request.registryAddress() != null) { return null; } return request.url().getPort(); diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index 732fdff31db3..976cc33601f5 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -37,14 +37,21 @@ public final class DubboRegistryUtil { /** * Used by {@link RegistryCapturingInvoker} while the cluster delegate runs (including into - * consumer protocol filters). + * consumer protocol filters). Returns the previous value so callers can restore it. */ - static void pushCapturedRegistryAddress(String address) { + @Nullable + static String pushCapturedRegistryAddress(String address) { + String previous = CAPTURED_REGISTRY_ADDRESS.get(); CAPTURED_REGISTRY_ADDRESS.set(address); + return previous; } - public static void clearCapturedRegistryAddress() { - CAPTURED_REGISTRY_ADDRESS.remove(); + public static void restoreCapturedRegistryAddress(@Nullable String previous) { + if (previous == null) { + CAPTURED_REGISTRY_ADDRESS.remove(); + } else { + CAPTURED_REGISTRY_ADDRESS.set(previous); + } } @Nullable @@ -70,17 +77,6 @@ public static String extractRegistryAddress(RpcInvocation invocation) { return null; } - /** - * Resolves {@code protocol://host:port} from a registry-backed directory (for example {@code - * RegistryDirectory}), using {@code getRegistry()} when present and otherwise the {@code - * registry} field. Called once per consumer refer when {@link RegistryCapturingClusterWrapper} - * wraps the cluster invoker. - */ - @Nullable - public static String tryExtractRegistryAddressFromDirectory(Directory directory) { - return extractRegistryAddressFromDirectory(directory); - } - public static String buildServiceTarget(URL url) { String interfaceName = url.getServiceInterface(); if (interfaceName == null || interfaceName.isEmpty()) { @@ -125,8 +121,14 @@ private static Directory getDirectory(Invoker invoker) { } } + /** + * Resolves {@code protocol://host:port} from a registry-backed directory (for example {@code + * RegistryDirectory}), using {@code getRegistry()} when present and otherwise the {@code + * registry} field. Called once per consumer refer when {@link RegistryCapturingClusterWrapper} + * wraps the cluster invoker. + */ @Nullable - private static String extractRegistryAddressFromDirectory(Directory directory) { + public static String tryExtractRegistryAddressFromDirectory(Directory directory) { MethodHandle getRegistry = findAccessor(directory.getClass(), "getRegistry", "registry", REGISTRY_ACCESSOR_CACHE); if (getRegistry == null) { @@ -183,7 +185,7 @@ private static MethodHandle resolveMethod(Class clazz, String name) { Method m = clazz.getMethod(name); m.setAccessible(true); return LOOKUP.unreflect(m); - } catch (NoSuchMethodException | IllegalAccessException e) { + } catch (NoSuchMethodException | IllegalAccessException ignored) { // ignore } return null; @@ -196,7 +198,7 @@ private static MethodHandle resolveField(Class clazz, String name) { Field f = c.getDeclaredField(name); f.setAccessible(true); return LOOKUP.unreflectGetter(f); - } catch (NoSuchFieldException | IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException ignored) { // ignore } } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java index d96fcac3a8f5..cf0410c431ff 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingInvoker.java @@ -49,11 +49,11 @@ public void destroy() { @Override public Result invoke(Invocation invocation) { - DubboRegistryUtil.pushCapturedRegistryAddress(registryAddress); + String previous = DubboRegistryUtil.pushCapturedRegistryAddress(registryAddress); try { return delegate.invoke(invocation); } finally { - DubboRegistryUtil.clearCapturedRegistryAddress(); + DubboRegistryUtil.restoreCapturedRegistryAddress(previous); } } } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java index 1dc777817680..8c3a38e51139 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java @@ -30,7 +30,7 @@ class DubboRegistryUtilTest { @AfterEach void tearDown() { - DubboRegistryUtil.clearCapturedRegistryAddress(); + DubboRegistryUtil.restoreCapturedRegistryAddress(null); } @ParameterizedTest @@ -76,7 +76,7 @@ void extractRegistryAddressFieldFallback() throws Exception { .isEqualTo("zookeeper://10.0.0.1:2181"); } - @SuppressWarnings("all") + @SuppressWarnings("UnusedVariable") abstract static class MockableRegistryDirectory implements Directory { Object registry; } diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java index 2d501e78ea31..2acffc636a34 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java @@ -183,17 +183,18 @@ void testRegistryModeServerAddress() throws ReflectiveOperationException { equalTo(SERVER_PORT, null), satisfies( NETWORK_PEER_ADDRESS, - k -> assertLatestDeps(k, a -> a.isInstanceOf(String.class))), + val -> + assertLatestDeps(val, v -> v.isInstanceOf(String.class))), satisfies( NETWORK_PEER_PORT, - k -> assertLatestDeps(k, a -> a.isInstanceOf(Long.class))), + val -> assertLatestDeps(val, v -> v.isInstanceOf(Long.class))), satisfies(NETWORK_TYPE, AbstractDubboTest::assertNetworkType)), span -> span.hasName( "io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService/hello") .hasKind(SpanKind.SERVER) .hasParent(trace.getSpan(1)) - .hasAttributesSatisfying( + .hasAttributesSatisfyingExactly( equalTo(RPC_SYSTEM, emitOldRpcSemconv() ? "apache_dubbo" : null), equalTo(RPC_SYSTEM_NAME, emitStableRpcSemconv() ? "dubbo" : null), equalTo( @@ -206,7 +207,9 @@ void testRegistryModeServerAddress() throws ReflectiveOperationException { emitStableRpcSemconv() ? "io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService/hello" : "hello"), - satisfies(NETWORK_PEER_ADDRESS, k -> k.isInstanceOf(String.class)), - satisfies(NETWORK_PEER_PORT, k -> k.isInstanceOf(Long.class))))); + satisfies( + NETWORK_PEER_ADDRESS, val -> val.isInstanceOf(String.class)), + satisfies( + NETWORK_PEER_PORT, val -> val.isInstanceOf(Long.class))))); } } From 26387ccac670631c13d24a7b92eb8c4997c9a37f Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Wed, 22 Apr 2026 14:34:35 +0800 Subject: [PATCH 08/14] feat: add service peer name handling in registry tests --- .../apachedubbo/v2_7/DubboAgentRegistryTest.java | 5 +++++ .../apachedubbo/v2_7/DubboRegistryTest.java | 5 +++++ .../apachedubbo/v2_7/AbstractDubboRegistryTest.java | 8 ++++++++ 3 files changed, 18 insertions(+) diff --git a/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java b/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java index 1079823d57c2..6a022484ba3e 100644 --- a/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/javaagent/src/testDubbo/java/io/opentelemetry/javaagent/instrumentation/apachedubbo/v2_7/DubboAgentRegistryTest.java @@ -19,4 +19,9 @@ class DubboAgentRegistryTest extends AbstractDubboRegistryTest { protected InstrumentationExtension testing() { return testing; } + + @Override + protected boolean hasServicePeerName() { + return true; + } } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java index b7eba1b367a4..61c01d0e768f 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRegistryTest.java @@ -18,4 +18,9 @@ class DubboRegistryTest extends AbstractDubboRegistryTest { protected InstrumentationExtension testing() { return testing; } + + @Override + protected boolean hasServicePeerName() { + return false; + } } diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java index 2acffc636a34..707d901f4862 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java @@ -9,6 +9,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitOldRpcSemconv; import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; +import static io.opentelemetry.instrumentation.testing.junit.service.SemconvServiceStabilityUtil.maybeStablePeerService; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -62,6 +63,8 @@ public abstract class AbstractDubboRegistryTest { protected abstract InstrumentationExtension testing(); + protected abstract boolean hasServicePeerName(); + @BeforeAll static void setUp() throws Exception { zkServer = new TestingServer(); @@ -207,6 +210,11 @@ void testRegistryModeServerAddress() throws ReflectiveOperationException { emitStableRpcSemconv() ? "io.opentelemetry.instrumentation.apachedubbo.v2_7.api.HelloService/hello" : "hello"), + equalTo( + maybeStablePeerService(), + hasServicePeerName() && Boolean.getBoolean("testLatestDeps") + ? "test-peer-service" + : null), satisfies( NETWORK_PEER_ADDRESS, val -> val.isInstanceOf(String.class)), satisfies( From 42bdec00e1d779b960baf364ac953c6581e71d2f Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Thu, 30 Apr 2026 21:40:55 +0800 Subject: [PATCH 09/14] Address review comments --- .../apachedubbo/v2_7/DubboRequest.java | 15 +++-- .../v2_7/internal/DubboRegistryUtil.java | 63 ++++++++----------- .../v2_7/internal/DubboRegistryUtilTest.java | 8 --- .../RegistryCapturingClusterWrapperTest.java | 1 - .../v2_7/AbstractDubboRegistryTest.java | 9 ++- 5 files changed, 42 insertions(+), 54 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java index d357cc808b84..1d8046e12de5 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/DubboRequest.java @@ -17,23 +17,19 @@ public abstract class DubboRequest { static DubboRequest create(RpcInvocation invocation, RpcContext context) { - // In dubbo 3 RpcContext delegates to a ThreadLocal context. We copy the url and remote address - // here to ensure we can access them from the thread that ends the span. + // In dubbo 3 RpcContext delegates to a ThreadLocal context. We copy the url, remote address, + // and registry address here to ensure we can access them from the thread that ends the span. return new AutoValue_DubboRequest( invocation, context, context.getUrl(), context.getRemoteAddress(), - context.getLocalAddress()); + context.getLocalAddress(), + DubboRegistryUtil.extractRegistryAddress(invocation)); } abstract RpcInvocation invocation(); - @Nullable - public String registryAddress() { - return DubboRegistryUtil.extractRegistryAddress(invocation()); - } - public abstract RpcContext context(); public abstract URL url(); @@ -43,4 +39,7 @@ public String registryAddress() { @Nullable public abstract InetSocketAddress localAddress(); + + @Nullable + public abstract String registryAddress(); } diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index 976cc33601f5..2033248e5640 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -9,9 +9,7 @@ import java.lang.invoke.MethodHandles; import java.lang.reflect.Field; import java.lang.reflect.Method; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; import javax.annotation.Nullable; import org.apache.dubbo.common.URL; import org.apache.dubbo.rpc.Invoker; @@ -26,12 +24,12 @@ public final class DubboRegistryUtil { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - private static final Map, Optional> DIRECTORY_ACCESSOR_CACHE = - new ConcurrentHashMap<>(); - private static final Map, Optional> REGISTRY_ACCESSOR_CACHE = - new ConcurrentHashMap<>(); - private static final Map, Optional> URL_ACCESSOR_CACHE = - new ConcurrentHashMap<>(); + private static final ClassValue> DIRECTORY_ACCESSOR = + createAccessor("getDirectory", "directory"); + private static final ClassValue> REGISTRY_ACCESSOR = + createAccessor("getRegistry", "registry"); + private static final ClassValue> URL_ACCESSOR = + createAccessor("getUrl", null); private static final ThreadLocal CAPTURED_REGISTRY_ADDRESS = new ThreadLocal<>(); @@ -108,8 +106,7 @@ public static String buildServiceTarget(URL url) { @Nullable private static Directory getDirectory(Invoker invoker) { - MethodHandle mh = - findAccessor(invoker.getClass(), "getDirectory", "directory", DIRECTORY_ACCESSOR_CACHE); + MethodHandle mh = DIRECTORY_ACCESSOR.get(invoker.getClass()).orElse(null); if (mh == null) { return null; } @@ -129,8 +126,7 @@ private static Directory getDirectory(Invoker invoker) { */ @Nullable public static String tryExtractRegistryAddressFromDirectory(Directory directory) { - MethodHandle getRegistry = - findAccessor(directory.getClass(), "getRegistry", "registry", REGISTRY_ACCESSOR_CACHE); + MethodHandle getRegistry = REGISTRY_ACCESSOR.get(directory.getClass()).orElse(null); if (getRegistry == null) { return null; } @@ -139,7 +135,7 @@ public static String tryExtractRegistryAddressFromDirectory(Directory directo if (registry == null) { return null; } - MethodHandle getUrl = findAccessor(registry.getClass(), "getUrl", null, URL_ACCESSOR_CACHE); + MethodHandle getUrl = URL_ACCESSOR.get(registry.getClass()).orElse(null); if (getUrl == null) { return null; } @@ -154,29 +150,24 @@ public static String tryExtractRegistryAddressFromDirectory(Directory directo } } - @Nullable - private static MethodHandle findAccessor( - Class clazz, - String methodName, - @Nullable String fieldName, - Map, Optional> cache) { - return cache - .computeIfAbsent( - clazz, - cls -> { - MethodHandle mh = resolveMethod(cls, methodName); - if (mh != null) { - return Optional.of(mh); - } - if (fieldName != null) { - mh = resolveField(cls, fieldName); - if (mh != null) { - return Optional.of(mh); - } - } - return Optional.empty(); - }) - .orElse(null); + private static ClassValue> createAccessor( + String methodName, @Nullable String fieldName) { + return new ClassValue>() { + @Override + protected Optional computeValue(Class type) { + MethodHandle mh = resolveMethod(type, methodName); + if (mh != null) { + return Optional.of(mh); + } + if (fieldName != null) { + mh = resolveField(type, fieldName); + if (mh != null) { + return Optional.of(mh); + } + } + return Optional.empty(); + } + }; } @Nullable diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java index 8c3a38e51139..337e1d4e7821 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtilTest.java @@ -16,23 +16,16 @@ import org.apache.dubbo.rpc.Result; import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.cluster.Directory; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -@SuppressWarnings("deprecation") class DubboRegistryUtilTest { private static final URL DUMMY_URL = URL.valueOf("dubbo://192.168.1.100:20880/com.example.Service"); - @AfterEach - void tearDown() { - DubboRegistryUtil.restoreCapturedRegistryAddress(null); - } - @ParameterizedTest @MethodSource("serviceTargetProvider") void buildServiceTarget(String urlString, String expected) { @@ -81,7 +74,6 @@ abstract static class MockableRegistryDirectory implements Directory { Object registry; } - @SuppressWarnings("unchecked") private static Directory mockDirectoryWithRegistry(Object registry) throws Exception { MockableRegistryDirectory dir = mock(MockableRegistryDirectory.class); Field f = MockableRegistryDirectory.class.getDeclaredField("registry"); diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java index 8eaae805598c..a806be55600f 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/test/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapperTest.java @@ -23,7 +23,6 @@ class RegistryCapturingClusterWrapperTest { URL.valueOf("dubbo://192.168.1.100:20880/com.example.Service"); @Test - @SuppressWarnings("unchecked") void joinDoesNotWrapStaticDirectory() { Invoker innerInvoker = new NoopInvoker(DUMMY_URL); RegistryCapturingClusterWrapper wrapper = diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java index 707d901f4862..644dd3a29970 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java @@ -58,6 +58,7 @@ public abstract class AbstractDubboRegistryTest { private static final String SERVICE_GROUP = "testGroup"; private static TestingServer zkServer; + private static InetAddress originalLocalAddress; @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); @@ -73,18 +74,24 @@ static void setUp() throws Exception { System.setProperty("dubbo.application.qos-enable", "false"); Field field = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); field.setAccessible(true); + originalLocalAddress = (InetAddress) field.get(null); field.set(null, InetAddress.getLoopbackAddress()); } @AfterAll static void tearDown() throws Exception { System.clearProperty("dubbo.application.qos-enable"); + if (originalLocalAddress != null) { + Field field = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); + field.setAccessible(true); + field.set(null, originalLocalAddress); + } if (zkServer != null) { zkServer.close(); } } - static String zkAddress() { + private static String zkAddress() { return "zookeeper://127.0.0.1:" + zkServer.getPort(); } From b11d3e72dc2a4213c882ec76b501fbb66eb505c6 Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Fri, 1 May 2026 12:23:44 +0800 Subject: [PATCH 10/14] Address review comments --- .../apachedubbo/v2_7/internal/DubboRegistryUtil.java | 5 ++--- .../v2_7/internal/RegistryCapturingClusterWrapper.java | 2 -- .../apachedubbo/v2_7/AbstractDubboRegistryTest.java | 2 +- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java index 2033248e5640..8736ab5f6632 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/DubboRegistryUtil.java @@ -44,7 +44,7 @@ static String pushCapturedRegistryAddress(String address) { return previous; } - public static void restoreCapturedRegistryAddress(@Nullable String previous) { + static void restoreCapturedRegistryAddress(@Nullable String previous) { if (previous == null) { CAPTURED_REGISTRY_ADDRESS.remove(); } else { @@ -125,7 +125,7 @@ private static Directory getDirectory(Invoker invoker) { * wraps the cluster invoker. */ @Nullable - public static String tryExtractRegistryAddressFromDirectory(Directory directory) { + static String tryExtractRegistryAddressFromDirectory(Directory directory) { MethodHandle getRegistry = REGISTRY_ACCESSOR.get(directory.getClass()).orElse(null); if (getRegistry == null) { return null; @@ -174,7 +174,6 @@ protected Optional computeValue(Class type) { private static MethodHandle resolveMethod(Class clazz, String name) { try { Method m = clazz.getMethod(name); - m.setAccessible(true); return LOOKUP.unreflect(m); } catch (NoSuchMethodException | IllegalAccessException ignored) { // ignore diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java index 5c96e45fa30d..01a3e52fd077 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -37,14 +37,12 @@ public final class RegistryCapturingClusterWrapper implements Cluster { MethodHandle one = null; try { Method m = Cluster.class.getMethod("join", Directory.class, boolean.class); - m.setAccessible(true); two = LOOKUP.unreflect(m); } catch (ReflectiveOperationException ignored) { // Dubbo 2.7 / 3.0.0-3.0.3 } try { Method m = Cluster.class.getMethod("join", Directory.class); - m.setAccessible(true); one = LOOKUP.unreflect(m); } catch (ReflectiveOperationException ignored) { // Dubbo 3.0.4+ diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java index 644dd3a29970..b53fb62c25ac 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java @@ -104,7 +104,7 @@ private static RegistryConfig newZkRegistryConfig(String zkAddr) { } @Test - void testRegistryModeServerAddress() throws ReflectiveOperationException { + void testRegistryModeServerAddress() throws Exception { int port = PortUtils.findOpenPort(); String zkAddr = zkAddress(); From 0800ea06bdf969b59f7b9ef331227bdd50d81d4d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 5 May 2026 00:47:29 -0700 Subject: [PATCH 11/14] Simlifications (#13) --- .../RegistryCapturingClusterWrapper.java | 27 ++----------------- .../v2_7/AbstractDubboRegistryTest.java | 23 +++++----------- 2 files changed, 8 insertions(+), 42 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java index 01a3e52fd077..a2e4d05620a1 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -29,26 +29,15 @@ public final class RegistryCapturingClusterWrapper implements Cluster { /** Dubbo 3.0.4+: join(Directory, boolean). Absent on 2.7 and 3.0.0-3.0.3. */ @Nullable private static final MethodHandle JOIN_TWO_ARG; - /** Dubbo 2.7 and 3.0.0-3.0.3: join(Directory). Absent on 3.0.4+. */ - @Nullable private static final MethodHandle JOIN_ONE_ARG; - static { MethodHandle two = null; - MethodHandle one = null; try { Method m = Cluster.class.getMethod("join", Directory.class, boolean.class); two = LOOKUP.unreflect(m); } catch (ReflectiveOperationException ignored) { // Dubbo 2.7 / 3.0.0-3.0.3 } - try { - Method m = Cluster.class.getMethod("join", Directory.class); - one = LOOKUP.unreflect(m); - } catch (ReflectiveOperationException ignored) { - // Dubbo 3.0.4+ - } JOIN_TWO_ARG = two; - JOIN_ONE_ARG = one; } private final Cluster cluster; @@ -61,7 +50,7 @@ public RegistryCapturingClusterWrapper(Cluster cluster) { @Override public Invoker join(Directory directory) { - return wrapIfNeeded(directory, delegateJoin(cluster, directory, true)); + return wrapIfNeeded(directory, cluster.join(directory)); } /** @@ -84,31 +73,19 @@ private static Invoker delegateJoin( if (JOIN_TWO_ARG != null) { return (Invoker) JOIN_TWO_ARG.invoke(cluster, directory, buildFilterChain); } - if (JOIN_ONE_ARG != null) { - return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); - } + return cluster.join(directory); } catch (RpcException e) { throw e; } catch (Throwable t) { throw new RpcException(t.getMessage(), t); } - throw new RpcException("No join(Directory) or join(Directory,boolean) on Cluster"); } private static Invoker wrapIfNeeded(Directory directory, Invoker invoker) { - if (isStaticDirectory(directory)) { - return invoker; - } String registryAddress = DubboRegistryUtil.tryExtractRegistryAddressFromDirectory(directory); if (registryAddress == null) { return invoker; } return new RegistryCapturingInvoker<>(invoker, registryAddress); } - - private static boolean isStaticDirectory(Directory directory) { - return directory != null - && "org.apache.dubbo.rpc.cluster.directory.StaticDirectory" - .equals(directory.getClass().getName()); - } } diff --git a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java index b53fb62c25ac..699bba6e464c 100644 --- a/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java +++ b/instrumentation/apache-dubbo-2.7/testing/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/AbstractDubboRegistryTest.java @@ -10,6 +10,7 @@ import static io.opentelemetry.instrumentation.api.internal.SemconvStability.emitStableRpcSemconv; import static io.opentelemetry.instrumentation.testing.GlobalTraceUtil.runWithSpan; import static io.opentelemetry.instrumentation.testing.junit.service.SemconvServiceStabilityUtil.maybeStablePeerService; +import static io.opentelemetry.instrumentation.testing.util.TestLatestDeps.testLatestDeps; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies; @@ -40,7 +41,6 @@ import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.bootstrap.DubboBootstrap; import org.apache.dubbo.rpc.service.GenericService; -import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; @@ -58,7 +58,6 @@ public abstract class AbstractDubboRegistryTest { private static final String SERVICE_GROUP = "testGroup"; private static TestingServer zkServer; - private static InetAddress originalLocalAddress; @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); @@ -69,26 +68,16 @@ public abstract class AbstractDubboRegistryTest { @BeforeAll static void setUp() throws Exception { zkServer = new TestingServer(); + cleanup.deferAfterAll(zkServer); zkServer.start(); System.setProperty("dubbo.application.qos-enable", "false"); + cleanup.deferAfterAll(() -> System.clearProperty("dubbo.application.qos-enable")); Field field = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); field.setAccessible(true); - originalLocalAddress = (InetAddress) field.get(null); + InetAddress originalLocalAddress = (InetAddress) field.get(null); field.set(null, InetAddress.getLoopbackAddress()); - } - - @AfterAll - static void tearDown() throws Exception { - System.clearProperty("dubbo.application.qos-enable"); - if (originalLocalAddress != null) { - Field field = NetUtils.class.getDeclaredField("LOCAL_ADDRESS"); - field.setAccessible(true); - field.set(null, originalLocalAddress); - } - if (zkServer != null) { - zkServer.close(); - } + cleanup.deferAfterAll(() -> field.set(null, originalLocalAddress)); } private static String zkAddress() { @@ -219,7 +208,7 @@ void testRegistryModeServerAddress() throws Exception { : "hello"), equalTo( maybeStablePeerService(), - hasServicePeerName() && Boolean.getBoolean("testLatestDeps") + hasServicePeerName() && testLatestDeps() ? "test-peer-service" : null), satisfies( From c01ecef134ffa9842d7fcc953d3f2320c234716b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 5 May 2026 11:40:08 -0700 Subject: [PATCH 12/14] Fix Dubbo cluster wrapper across join signatures Dubbo 3.0.4+ removed the one-argument Cluster.join(Directory) method, but RegistryCapturingClusterWrapper still referenced it directly. This caused latest-deps tests to throw NoSuchMethodError and muzzle to reject Dubbo 3.x. Resolve both Cluster.join signatures through method handles and dispatch to the signature available at runtime, avoiding hard bytecode references to removed methods while preserving Dubbo 2.7 behavior. Validation: .\gradlew.bat :instrumentation:apache-dubbo-2.7:library-autoconfigure:test -PtestLatestDeps=true --tests io.opentelemetry.instrumentation.apachedubbo.v2_7.internal.RegistryCapturingClusterWrapperTest .\gradlew.bat :instrumentation:apache-dubbo-2.7:javaagent:testDubbo :instrumentation:apache-dubbo-2.7:javaagent:testDubboStableSemconv :instrumentation:apache-dubbo-2.7:javaagent:testDubboBothSemconv :instrumentation:apache-dubbo-2.7:javaagent:muzzle -PtestLatestDeps=true Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../RegistryCapturingClusterWrapper.java | 46 +++++++++++++++++-- 1 file changed, 43 insertions(+), 3 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java index a2e4d05620a1..ab1177e5e04f 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -26,10 +26,21 @@ public final class RegistryCapturingClusterWrapper implements Cluster { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); + /** Dubbo 2.7 and 3.0.0-3.0.3: join(Directory). Removed in 3.0.4. */ + @Nullable private static final MethodHandle JOIN_ONE_ARG; + /** Dubbo 3.0.4+: join(Directory, boolean). Absent on 2.7 and 3.0.0-3.0.3. */ @Nullable private static final MethodHandle JOIN_TWO_ARG; static { + MethodHandle one = null; + try { + Method m = Cluster.class.getMethod("join", Directory.class); + one = LOOKUP.unreflect(m); + } catch (ReflectiveOperationException ignored) { + // Dubbo 3.0.4+ + } + MethodHandle two = null; try { Method m = Cluster.class.getMethod("join", Directory.class, boolean.class); @@ -37,6 +48,7 @@ public final class RegistryCapturingClusterWrapper implements Cluster { } catch (ReflectiveOperationException ignored) { // Dubbo 2.7 / 3.0.0-3.0.3 } + JOIN_ONE_ARG = one; JOIN_TWO_ARG = two; } @@ -50,7 +62,7 @@ public RegistryCapturingClusterWrapper(Cluster cluster) { @Override public Invoker join(Directory directory) { - return wrapIfNeeded(directory, cluster.join(directory)); + return wrapIfNeeded(directory, delegateJoin(cluster, directory)); } /** @@ -64,7 +76,28 @@ public Invoker join(Directory directory, boolean buildFilterChain) { /** * {@link MethodHandle#invoke} is untyped; the returned invoker matches the generic {@code T} from - * the {@code directory} argument and delegate {@link Cluster#join}. + * the {@code directory} argument and delegate {@code Cluster.join}. + */ + @SuppressWarnings("unchecked") + private static Invoker delegateJoin(Cluster cluster, Directory directory) { + try { + if (JOIN_ONE_ARG != null) { + return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); + } + if (JOIN_TWO_ARG == null) { + throw noClusterJoinMethod(); + } + return (Invoker) JOIN_TWO_ARG.invoke(cluster, directory, true); + } catch (RpcException e) { + throw e; + } catch (Throwable t) { + throw new RpcException(t.getMessage(), t); + } + } + + /** + * {@link MethodHandle#invoke} is untyped; the returned invoker matches the generic {@code T} from + * the {@code directory} argument and delegate {@code Cluster.join}. */ @SuppressWarnings("unchecked") private static Invoker delegateJoin( @@ -73,7 +106,10 @@ private static Invoker delegateJoin( if (JOIN_TWO_ARG != null) { return (Invoker) JOIN_TWO_ARG.invoke(cluster, directory, buildFilterChain); } - return cluster.join(directory); + if (JOIN_ONE_ARG == null) { + throw noClusterJoinMethod(); + } + return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); } catch (RpcException e) { throw e; } catch (Throwable t) { @@ -88,4 +124,8 @@ private static Invoker wrapIfNeeded(Directory directory, Invoker in } return new RegistryCapturingInvoker<>(invoker, registryAddress); } + + private static RpcException noClusterJoinMethod() { + return new RpcException("No supported org.apache.dubbo.rpc.cluster.Cluster.join method found"); + } } From 1202b5cd38f0b0c2011bd2c9a1656799a5bea6ca Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 5 May 2026 12:04:01 -0700 Subject: [PATCH 13/14] revert a bit more --- .../RegistryCapturingClusterWrapper.java | 57 ++++++------------- 1 file changed, 16 insertions(+), 41 deletions(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java index ab1177e5e04f..2d4e2d657baa 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -26,30 +26,29 @@ public final class RegistryCapturingClusterWrapper implements Cluster { private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup(); - /** Dubbo 2.7 and 3.0.0-3.0.3: join(Directory). Removed in 3.0.4. */ - @Nullable private static final MethodHandle JOIN_ONE_ARG; - /** Dubbo 3.0.4+: join(Directory, boolean). Absent on 2.7 and 3.0.0-3.0.3. */ @Nullable private static final MethodHandle JOIN_TWO_ARG; - static { - MethodHandle one = null; - try { - Method m = Cluster.class.getMethod("join", Directory.class); - one = LOOKUP.unreflect(m); - } catch (ReflectiveOperationException ignored) { - // Dubbo 3.0.4+ - } + /** Dubbo 2.7 and 3.0.0-3.0.3: join(Directory). Absent on 3.0.4+. */ + @Nullable private static final MethodHandle JOIN_ONE_ARG; + static { MethodHandle two = null; + MethodHandle one = null; try { Method m = Cluster.class.getMethod("join", Directory.class, boolean.class); two = LOOKUP.unreflect(m); } catch (ReflectiveOperationException ignored) { // Dubbo 2.7 / 3.0.0-3.0.3 } - JOIN_ONE_ARG = one; + try { + Method m = Cluster.class.getMethod("join", Directory.class); + one = LOOKUP.unreflect(m); + } catch (ReflectiveOperationException ignored) { + // Dubbo 3.0.4+ + } JOIN_TWO_ARG = two; + JOIN_ONE_ARG = one; } private final Cluster cluster; @@ -62,7 +61,7 @@ public RegistryCapturingClusterWrapper(Cluster cluster) { @Override public Invoker join(Directory directory) { - return wrapIfNeeded(directory, delegateJoin(cluster, directory)); + return wrapIfNeeded(directory, delegateJoin(cluster, directory, true)); } /** @@ -76,28 +75,7 @@ public Invoker join(Directory directory, boolean buildFilterChain) { /** * {@link MethodHandle#invoke} is untyped; the returned invoker matches the generic {@code T} from - * the {@code directory} argument and delegate {@code Cluster.join}. - */ - @SuppressWarnings("unchecked") - private static Invoker delegateJoin(Cluster cluster, Directory directory) { - try { - if (JOIN_ONE_ARG != null) { - return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); - } - if (JOIN_TWO_ARG == null) { - throw noClusterJoinMethod(); - } - return (Invoker) JOIN_TWO_ARG.invoke(cluster, directory, true); - } catch (RpcException e) { - throw e; - } catch (Throwable t) { - throw new RpcException(t.getMessage(), t); - } - } - - /** - * {@link MethodHandle#invoke} is untyped; the returned invoker matches the generic {@code T} from - * the {@code directory} argument and delegate {@code Cluster.join}. + * the {@code directory} argument and delegate {@link Cluster#join}. */ @SuppressWarnings("unchecked") private static Invoker delegateJoin( @@ -106,15 +84,15 @@ private static Invoker delegateJoin( if (JOIN_TWO_ARG != null) { return (Invoker) JOIN_TWO_ARG.invoke(cluster, directory, buildFilterChain); } - if (JOIN_ONE_ARG == null) { - throw noClusterJoinMethod(); + if (JOIN_ONE_ARG != null) { + return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); } - return (Invoker) JOIN_ONE_ARG.invoke(cluster, directory); } catch (RpcException e) { throw e; } catch (Throwable t) { throw new RpcException(t.getMessage(), t); } + throw new RpcException("No join(Directory) or join(Directory,boolean) on Cluster"); } private static Invoker wrapIfNeeded(Directory directory, Invoker invoker) { @@ -125,7 +103,4 @@ private static Invoker wrapIfNeeded(Directory directory, Invoker in return new RegistryCapturingInvoker<>(invoker, registryAddress); } - private static RpcException noClusterJoinMethod() { - return new RpcException("No supported org.apache.dubbo.rpc.cluster.Cluster.join method found"); - } } From 84798085ec0c7afd3a68b3b228b7caec031cecbd Mon Sep 17 00:00:00 2001 From: Steve Rao Date: Wed, 6 May 2026 10:10:47 +0800 Subject: [PATCH 14/14] refactor: remove unnecessary newline in RegistryCapturingClusterWrapper --- .../v2_7/internal/RegistryCapturingClusterWrapper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java index 2d4e2d657baa..d4001e1e0e8e 100644 --- a/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java +++ b/instrumentation/apache-dubbo-2.7/library-autoconfigure/src/main/java/io/opentelemetry/instrumentation/apachedubbo/v2_7/internal/RegistryCapturingClusterWrapper.java @@ -102,5 +102,4 @@ private static Invoker wrapIfNeeded(Directory directory, Invoker in } return new RegistryCapturingInvoker<>(invoker, registryAddress); } - }