跳转到主内容
websoft网络软件专家 - 深耕网络技术,打造实用软件!

Java面试之Java序列化与反序列化

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

相关文章