Java 配置已经只读却还在变:区分 Map 视图与快照
配置中心返回了一个无法 put 的 Map,调用方却发现值在热更新后变了。Collections.unmodifiableMap 是底层 Map 的只读视图,后续修改仍会透过视图。Map.copyOf 可以固定映射关系,内部的可变对象还需要单独复制。
1. 三次修改验证三层边界
保存为 Lab.java,运行 java -ea Lab.java。
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class Lab {
public static void main(String[] args) {
var routes = new ArrayList<>(List.of("/health"));
var source = new HashMap<String, List<String>>();
source.put("public", routes);
var view = Collections.unmodifiableMap(source);
var shallow = Map.copyOf(source);
var detached = new HashMap<String, List<String>>();
source.forEach((key, value) -> detached.put(key, List.copyOf(value)));
var snapshot = Map.copyOf(detached);
source.put("admin", List.of("/admin"));
assert view.containsKey("admin");
assert !shallow.containsKey("admin");
System.out.println("new map entry: view=true, copy=false");
routes.add("/metrics");
assert shallow.get("public").size() == 2;
assert snapshot.get("public").equals(List.of("/health"));
System.out.println("nested list sizes: shallow=2, snapshot=1");
try {
view.put("other", List.of());
throw new AssertionError("expected read-only view");
} catch (UnsupportedOperationException expected) {
System.out.println("direct mutation: rejected by view");
}
}
}
本机实测输出如下。
new map entry: view=true, copy=false
nested list sizes: shallow=2, snapshot=1
direct mutation: rejected by view
2. 按配置模型冻结每一层
| 方法 | 固定了什么 |
|---|---|
| unmodifiableMap | 阻止通过这个引用修改映射 |
| Map.copyOf | 固定键和值的引用关系 |
| Map.copyOf 配合 List.copyOf | 固定映射和列表的成员关系 |
示例的叶子是不可变 String,因此复制 Map 和 List 就足够。若叶子是可变 DTO,还需要转换为不可变值。copyOf 拒绝 null,接入旧配置前要决定缺失、空值和空列表的含义。
热更新时,构造并校验完整快照,再通过 volatile 字段或 AtomicReference 发布新引用。读取者每次业务操作只取一次引用,避免在同一次操作里交叉读取两个版本。制作副本时也要确保源数据稳定,例如从刚解析、尚未共享的本地对象构造。
小结
发布配置前同时验证映射和嵌套值的生命周期。用源对象后续修改来检验快照是否真正独立。
数据来源与检索时间
检索时间 2026-11-13。
| 来源 | URL | 对应内容 |
|---|---|---|
| Java 17 Map.copyOf | https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Map.html#copyOf(java.util.Map) | 快照与不可修改映射约束 |
| Java 17 Collections | https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/Collections.html#unmodifiableMap(java.util.Map) | 只读视图对底层映射的读取 |
| Java 17 List | https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/List.html#copyOf(java.util.Collection) | 列表副本与空值限制 |
核验说明
- OpenJDK 17.0.19 实测映射、嵌套列表和修改视图的三种行为。并发发布方式是接入建议。