From 3e65d53d0b4145c0e17dfb4df3693f0a1907e6c0 Mon Sep 17 00:00:00 2001 From: Jason Yao Date: Sat, 6 Jun 2026 00:59:56 -0400 Subject: [PATCH] Fix UnsupportedOperationException in DubboDefaultPropertiesEnvironmentPostProcessor on immutable defaultProperties (#16268) --- ...ultPropertiesEnvironmentPostProcessor.java | 10 ++++-- ...ropertiesEnvironmentPostProcessorTest.java | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/dubbo-spring-boot-project/dubbo-spring-boot/src/main/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessor.java b/dubbo-spring-boot-project/dubbo-spring-boot/src/main/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessor.java index c2180f6a48e5..3712cc0cff12 100644 --- a/dubbo-spring-boot-project/dubbo-spring-boot/src/main/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessor.java +++ b/dubbo-spring-boot-project/dubbo-spring-boot/src/main/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessor.java @@ -127,13 +127,17 @@ private void addOrReplace(MutablePropertySources propertySources, Map source = propertySources.get(PROPERTY_SOURCE_NAME); if (source instanceof MapPropertySource) { - target = (MapPropertySource) source; + // The existing backing map may be immutable, so copy into a mutable map and replace the + // source instead of mutating it in place. See dubbo#16268. + Map merged = new HashMap<>(((MapPropertySource) source).getSource()); for (Map.Entry entry : map.entrySet()) { String key = entry.getKey(); - if (!target.containsProperty(key)) { - target.getSource().put(key, entry.getValue()); + if (!merged.containsKey(key)) { + merged.put(key, entry.getValue()); } } + target = new MapPropertySource(PROPERTY_SOURCE_NAME, merged); + propertySources.replace(PROPERTY_SOURCE_NAME, target); } } if (target == null) { diff --git a/dubbo-spring-boot-project/dubbo-spring-boot/src/test/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessorTest.java b/dubbo-spring-boot-project/dubbo-spring-boot/src/test/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessorTest.java index 06e4625d6a45..2dfa5ffcecc6 100644 --- a/dubbo-spring-boot-project/dubbo-spring-boot/src/test/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessorTest.java +++ b/dubbo-spring-boot-project/dubbo-spring-boot/src/test/java/org/apache/dubbo/spring/boot/env/DubboDefaultPropertiesEnvironmentPostProcessorTest.java @@ -16,7 +16,9 @@ */ package org.apache.dubbo.spring.boot.env; +import java.util.Collections; import java.util.HashMap; +import java.util.Map; import org.junit.jupiter.api.Test; import org.springframework.boot.SpringApplication; @@ -26,6 +28,7 @@ import org.springframework.core.env.PropertySource; import org.springframework.mock.env.MockEnvironment; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -106,4 +109,35 @@ void testPostProcessEnvironment() { assertNotNull(defaultPropertySource); assertEquals("virtual", defaultPropertySource.getProperty("dubbo.protocol.threadpool")); } + + /** #16268: addOrReplace must not fail when "defaultProperties" is backed by an immutable map. */ + @Test + void testPostProcessEnvironmentOnImmutableDefaultProperties() { + MockEnvironment environment = new MockEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + propertySources.addLast( + new MapPropertySource("defaultProperties", Collections.unmodifiableMap(new HashMap()))); + + instance.postProcessEnvironment(environment, springApplication); + + PropertySource defaultPropertySource = propertySources.get("defaultProperties"); + assertNotNull(defaultPropertySource); + assertEquals("true", defaultPropertySource.getProperty("dubbo.config.multiple")); + } + + /** #16268: on an immutable "defaultProperties", Dubbo's default must not override an existing key. */ + @Test + void testImmutableDefaultPropertiesDoesNotOverrideExistingKey() { + MockEnvironment environment = new MockEnvironment(); + MutablePropertySources propertySources = environment.getPropertySources(); + Map existing = new HashMap<>(); + existing.put("dubbo.config.multiple", "false"); + propertySources.addLast(new MapPropertySource("defaultProperties", Collections.unmodifiableMap(existing))); + + assertDoesNotThrow(() -> instance.postProcessEnvironment(environment, springApplication)); + + PropertySource defaultPropertySource = propertySources.get("defaultProperties"); + assertNotNull(defaultPropertySource); + assertEquals("false", defaultPropertySource.getProperty("dubbo.config.multiple")); + } }