collections.unmodifiableset仅提供运行时修改拦截,不阻断原始引用泄露;若原始set被其他变量持有并修改,视图将同步反映脏数据,真正防护需先防御性拷贝(如new hashset或set.copyof)再封装。

在 Spring 应用中,将配置属性以不可变方式暴露给业务代码,是避免意外修改、提升系统健壮性的常见实践。Collections.unmodifiableSet 正是实现这一目标的轻量级工具之一——它不改变原始集合,而是返回一个“只读视图”,任何修改操作都会立即抛出 UnsupportedOperationException。
为什么用 unmodifiableSet 而不是直接返回 HashSet 或 LinkedHashSet
Spring 的 @ConfigurationProperties 通常绑定到一个可变集合字段(如 Set
- 原始 Set 仍可由配置刷新机制安全更新(如 RefreshScope)
- 业务层拿到的是防御性副本视图,无法破坏配置一致性
- 相比 Collections.unmodifiableCollection 或手动 new LinkedHashSet(original),更语义明确且类型保留
典型配置类中的正确用法
定义一个配置类,用 @ConfigurationProperties 绑定 yml 属性,并在 getter 中封装为不可变 Set:
// application.yml
app:
allowed-roles: [ADMIN, USER, GUEST]
@ConfigurationProperties("app")
public class AppProperties {
private Set
public Set
return Collections.unmodifiableSet(allowedRoles);
}
// setter 留给 Spring 内部使用,不对外暴露
public void setAllowedRoles(Set
this.allowedRoles.clear();
this.allowedRoles.addAll(allowedRoles);
}
}
注意:setAllowedRoles 中先 clear 再 addAll,确保引用不变,unmodifiableSet 视图始终有效;若直接赋值 new HashSet,旧视图会失效。
配合 @Validated 和自定义校验的注意事项
如果对 allowedRoles 做 @NotEmpty 或自定义约束(如每个 role 必须大写),校验发生在绑定阶段,作用于原始可变 Set;而 unmodifiableSet 返回后,校验结果已确定,不影响只读语义。但需注意:
- 校验注解应加在字段上(而非 getter),否则不生效
- 若需运行时动态校验(如检查角色是否存在),应在 service 层基于不可变 Set 查询,而非尝试修改它
- 避免在 getter 内做耗时计算或重复构造新不可变包装——unmodifiableSet 是廉价的,但反复调用仍不如缓存一次
替代方案对比:什么时候不该用它
unmodifiableSet 适合“读多写少、写仅由框架控制”的场景。以下情况建议换方式:
- 需要深度不可变(如 Set 元素本身也需不可变),则应确保元素是 String/Integer 等天然不可变类型,或用 ImmutableSet.copyOf(来自 Guava)
- 配置需频繁刷新且要求线程安全视图,unmodifiableSet 本身不保证线程安全——原始 Set 若被并发修改,可能导致迭代异常;此时应结合 CopyOnWriteArraySet 或同步块
- 想彻底切断与原始集合的联系(防止反射篡改),unmodifiableSet 仍共享底层引用,Guava 的 ImmutableSet 或 JDK 10+ 的 Set.copyOf 更严格
不复杂但容易忽略:真正关键的不是“加不加 unmodifiableSet”,而是谁拥有写权限、何时写、是否线程可见——把它当作契约声明,而不是银弹。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











