“Constructor Hijacking”并非标准术语,实指构造函数中调用可重写成员导致子类字段未初始化即触发脱敏等副作用;安全做法是将脱敏移至序列化或getter中,与构造解耦。

“Constructor Hijacking”并不是一个标准的、被广泛认可的设计模式或安全术语,在 .NET、Java 或 WPF 官方文档中均无此命名。你提到的这个说法,很可能是对 构造函数中不当调用可重写成员所引发的初始化时序问题 的一种非正式描述——尤其在 DependencyObject 或继承链较深的类中,若父类构造函数提前触发了子类重写的回调(如 OnPropertyChanged、PropertyChangedCallback),就可能造成子类字段未初始化却已执行脱敏逻辑,即所谓“被劫持”的副作用。
但请注意:脱敏本身不是构造阶段该做的事,强制在构造器里做脱敏不仅违背单一职责,更会放大这类风险。真正安全、可维护的做法,是把脱敏从构造过程剥离,移到数据输出阶段(如序列化)或访问阶段(如 getter 封装)。
下面分三部分说明如何在继承体系中安全实现父类属性的脱敏:
避免在构造函数中触发脱敏
父类(比如 BaseUser)若定义了敏感字段(如 IdCard、Phone),切勿在构造函数里调用 SetValue 或直接赋值后立即脱敏。否则一旦子类重写了 OnPropertyChanged 或注册了 PropertyChangedCallback,脱敏逻辑就可能在子类字段初始化前执行,导致空引用或逻辑错乱。
正确做法是:构造函数只做最小必要初始化(如 ID 分配、状态设为 Pending),不碰业务值,更不触发任何回调。
用注解 + 序列化器统一控制脱敏时机
让脱敏发生在 JSON 输出时,与构造完全解耦。例如:
- 定义注解
@Sensitive(type = SensitiveType.ID_CARD) - 在父类字段上标注:
@Sensitive(type = SensitiveType.ID_CARD) protected String idCard; - 子类继承该字段,无需重复标注,脱敏规则自动沿用
- Jackson 的
ContextualSerializer在序列化时读取注解,动态选择脱敏策略(如用 Hutool 的DesensitizedUtil.idCard(idCard))
通过受保护的 getter 实现按需脱敏
如果必须在 Java/Kotlin/Scala 等语言中支持运行时差异化脱敏(比如管理员看明文、普通用户看脱敏),可在父类中定义:
protected String getIdCard() {
return currentUser.hasPermission("VIEW_RAW") ? this.idCard : DesensitizedUtil.idCard(this.idCard);
}子类可重写该 getter,添加自己的权限判断或渠道逻辑,而不会干扰构造流程。这种方式天然兼容继承,且不依赖框架回调机制。
不复杂但容易忽略:脱敏不是初始化任务,而是呈现策略。把它塞进构造器,等于把“怎么展示”混进了“我是谁”的定义里。

















