显式声明serialVersionUID可避免因类结构变动导致反序列化失败;transient字段不影响serialVersionUID校验;自定义readObject/writeObject需严格遵循签名并调用defaultRead/WriteObject以保证兼容性与安全性。
Java序列化不是“实现
接口就完事了”,它在真实项目中常因版本兼容、安全限制、性能误用而出问题——尤其在分布式通信、缓存、日志回溯等场景下,反序列化失败或被利用的风险远高于开发者的直觉预期。
为什么
不显式声明就会出问题?
JVM在序列化时会为未声明
的类自动生成一个值,该值基于类名、字段、方法签名等结构计算。只要类结构稍有变动(比如加个
字段、改个方法返回类型),生成的
就不同,导致反序列化时抛出
。
实操建议:
所有实现
的类,都应显式声明
(推荐用IDE生成带校验和的值,如
)
如果类已上线且被序列化数据持久化(如Redis中存了byte[]),升级时字段增减必须配合
保持不变,否则旧数据无法读取
字段不会参与序列化,但它的存在不影响
校验——这点常被误认为“加transient就能绕过兼容性检查”,其实不能
和
怎么安全地自定义序列化逻辑?
默认序列化会把所有非
非
字段全量写入,但实际中常需加密敏感字段、跳过不可序列化对象(如
)、或做深拷贝校验。这时要重写
和
。
立即学习
“
Java免费学习笔记(深入)
”;
注意点:
Eclipse导入Android或其他的JAVA项目的正确方法 WORD版
本文档主要讲述的是Eclipse导入Android或其他的JAVA项目的正确方法;希望本文档会给有需要的朋友带来帮助;感兴趣的朋友可以过来看看
下载
必须是
、
返回、参数严格匹配(
/
),否则JVM直接忽略,走默认逻辑
重写后,第一行通常要调用
或
,否则非
字段不会被自动处理
在
里可做反序列化后校验,比如检查
是否为正数、时间戳是否合理,非法则直接抛
为什么
反序列化可能被RCE攻击?
Java原生反序列化不校验类名,只要字节流里指定了某个类(如
),
就会尝试加载并实例化它——这正是很多反序列化漏洞(如WebLogic、Jackson gadget链)的根源。
生产环境必须规避:
禁用高危类:通过
子类重写
,白名单控制可反序列化的类,例如只允许
包下的类
避免在网络边界直接反序列化外部输入:HTTP请求体、MQ消息、Redis value等,一律视为不可信;优先用JSON/Protobuf替代
JDK 9+ 可配置系统属性
(如
),但注意它仅作用于全局
,对第三方库(如Apache Commons Collections)中的反序列化调用无效
序列化选型:什么时候不该用Java原生序列化?
原生序列化耦合JVM版本、不跨语言、体积大、无向后兼容保障。以下场景应明确拒绝:
微服务间RPC通信(如Dubbo默认用Hessian,Spring Cloud用JSON)——Java序列化无法被Go/Python服务消费
长期存储(如数据库BLOB、文件归档)——JDK升级后很可能无法反序列化
高频小对象(如秒级埋点日志)——相比Kryo或FST,原生序列化慢3–5倍,GC压力明显
需要部分字段更新(如只改用户头像URL)——原生序列化只能全量读写,没法像Protobuf那样用
局部构建
真正需要Java原生序列化的典型场景只剩两个:JVM进程内缓存(如Ehcache 2.x)、或遗留系统强依赖
等自带序列化逻辑的集合类——其他情况,优先考虑
(Jackson/Gson)、
、或
(需注册类,关闭引用跟踪以提升性能)。
SerializableserialVersionUIDserialVersionUIDprivateserialVersionUIDInvalidClassExceptionSerializableprivate static final long serialVersionUID = 1L;1234567890123456789LserialVersionUIDtransientserialVersionUIDreadObject()writeObject()transientstaticThreadprivate void writeObject(ObjectOutputStream out)private void readObject(ObjectInputStream in)privatevoidObjectOutputStreamObjectInputStreamout.defaultWriteObject()in.defaultReadObject()transientreadObject()userIdInvalidObjectExceptionprivate void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
if (this.userId <= 0) {
throw new InvalidObjectException("userId must be positive");
}
}ObjectInputStreamorg.apache.commons.collections.functors.InvokerTransformerObjectInputStreamObjectInputStreamresolveClass()com.myapp.dto.*jdk.serialFilter-Djdk.serialFilter=java.lang.String;com.myapp.**ObjectInputStreamBuilderjava.util.TreeSetJSONProtobufKryo