真正起效的防护是配置实例本身的不可变性,而非运行时拦截;需将字段设为private final、禁用setter、用@ConstructorBinding构造绑定、嵌套集合返回不可变视图、getter防御性返回副本。

不能通过属性拦截器防范全局配置对象被第三方业务污染。
真正起效的防护是配置实例本身的不可变性,而不是运行时拦截行为。Spring 的拦截器(如 HandlerInterceptor)只作用于 Web 请求处理链路,对已创建并注入 Spring 容器的全局配置对象(如 @ConfigurationProperties 绑定的 Bean)完全无效——只要业务代码持有该对象引用,就能直接调用 setter、修改内部集合或通过反射篡改字段,拦截器对此无感知、无约束。
配置对象必须设计为不可变
- 所有字段声明为
private final,禁止提供 public setter 方法 - 构造时一次性完成初始化,不预留任何修改入口
- 使用
@ConstructorBinding(Spring Boot 2.2+)强制构造器绑定,绕过默认的 setter 绑定机制
嵌套结构也要冻结
- 若配置含
List、Set或Map,不能返回原始可变容器 - 用
Collections.unmodifiableList()、ImmutableList.copyOf()(Guava)等封装为不可变视图 - 避免下游拿到引用后调用
add()、put()等方法破坏状态
Getter 必须防御性返回
- 不允许
return this.features;这类直接返回内部引用的写法 - 应返回副本或不可变包装:
return Collections.unmodifiableSet(new HashSet<>(this.features)); - 对于简单值类型(如
int、String)无需额外处理,但String本身不可变,StringBuilder等则需警惕
不要依赖拦截器做“防篡改”伪装
- 在拦截器里校验请求参数、清洗入参,和防止配置对象被修改是两回事
-
@Validated仅校验初始化或绑定阶段的数据合法性,不阻止后续代码对实例的修改 - 把
public static final Map直接暴露出去,哪怕加了拦截器,仍可通过map.put()或反射修改
本质上,防污染的关键不在“拦”,而在“封”——封住所有可写路径,让篡改在编译期或运行初期就无法发生。

















