diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java index 13b0cae5badb..1c57ce86e1d9 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/JsonUtils.java @@ -24,6 +24,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.TreeMap; @@ -79,10 +80,9 @@ private static JsonUtil createJsonUtil() { private static JsonUtil loadExtensions(String name, ClassLoader classLoader, Map extensions) { ServiceLoader loader = ServiceLoader.load(JsonUtil.class, classLoader); Iterator it = loader.iterator(); - // In JDK 21+, ServiceLoader.hasNext() may throw NoClassDefFoundError - // when checking class dependencies, so we need to catch it here while (true) { try { + // ServiceLoader may resolve provider constructors in hasNext(), before next() can be called. if (!it.hasNext()) { break; } @@ -93,9 +93,7 @@ private static JsonUtil loadExtensions(String name, ClassLoader classLoader, Map } extensions.put(extension.getName(), extension); } - } catch (Throwable ignored) { - // Ignore loading failures (e.g., NoClassDefFoundError in JDK 25) - // and continue with the next extension + } catch (ServiceConfigurationError | LinkageError | RuntimeException ignored) { } } return null; diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/JsonUtilsTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/JsonUtilsTest.java index 6fb8721e45fc..c6c3a0846852 100644 --- a/dubbo-common/src/test/java/org/apache/dubbo/common/utils/JsonUtilsTest.java +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/utils/JsonUtilsTest.java @@ -27,6 +27,9 @@ import org.apache.dubbo.common.utils.json.TestObjectB; import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; import java.util.Collections; import java.util.HashMap; import java.util.LinkedList; @@ -396,6 +399,42 @@ void testGetJson2() { setJson(null); } + @Test + void testLoadExtensionsWithoutOptionalGsonDependency() throws Exception { + URL classes = GsonImpl.class.getProtectionDomain().getCodeSource().getLocation(); + Map extensions = new HashMap<>(); + + try (URLClassLoader classLoader = new URLClassLoader(new URL[] {classes}, JsonUtils.class.getClassLoader()) { + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + if (name.startsWith("com.google.gson.")) { + throw new ClassNotFoundException(name); + } + if (name.equals("org.apache.dubbo.common.json.impl.GsonImpl")) { + synchronized (getClassLoadingLock(name)) { + Class loaded = findLoadedClass(name); + if (loaded == null) { + loaded = findClass(name); + } + if (resolve) { + resolveClass(loaded); + } + return loaded; + } + } + return super.loadClass(name, resolve); + } + }) { + Method loadExtensions = + JsonUtils.class.getDeclaredMethod("loadExtensions", String.class, ClassLoader.class, Map.class); + loadExtensions.setAccessible(true); + loadExtensions.invoke(null, null, classLoader, extensions); + } + + Assertions.assertInstanceOf(FastJson2Impl.class, extensions.get("fastjson2")); + Assertions.assertFalse(extensions.containsKey("gson")); + } + private static Field jsonFieldCache; /**