在 java 对象序列化中处理 ejb,核心原则是避免直接序列化 ejb 的远程或本地引用。由于 ejb 引用本质上是代理对象(proxy),包含复杂的网络状态和容器管理上下文,直接序列化会导致 `notserializableexception` 或产生无法反序列化的无效数据。正确的做法是在实体类中不直接持有 ejb 引用,而是通过 id、jndi 名称或外部查找机制在反序列化后重新获取 ejb 实例。

为什么不能直接序列化 EJB 引用
直接尝试序列化一个包含 EJB 注入字段(如 @EJB)的对象会失败。这是因为 EJB 容器生成的代理对象通常不实现 Serializable 接口,或者其内部状态依赖于特定的 JVM 内存地址和容器环境。当序列化流试图遍历对象图时,遇到这些非可序列化的代理节点就会抛出异常。此外,即使某些轻量级代理实现了序列化,反序列化后的对象也无法连接到原始的 EJB 容器,导致调用失效。

import java.io.Serializable;
public class OrderServiceClient implements Serializable {
private static final long serialVersionUID = 1L;
// 错误做法:直接序列化 EJB 引用
// @EJB
// private PaymentGateway paymentGateway;
// 正确做法:只序列化业务数据
private String orderId;
private double amount;
private String status;
public OrderServiceClient(String orderId, double amount) {
this.orderId = orderId;
this.amount = amount;
this.status = "PENDING";
}
public void processPayment() {
// 运行时动态查找 EJB,而非从序列化流中恢复
try {
javax.naming.Context ctx = new javax.naming.InitialContext();
PaymentGateway gateway = (PaymentGateway) ctx.lookup("java:global/MyApp/PaymentGateway");
gateway.charge(amount);
} catch (Exception e) {
throw new RuntimeException("Failed to lookup EJB", e);
}
}
}
上述代码展示了标准的 DTO(数据传输对象)模式。客户端对象仅包含基本数据类型和字符串,确保完全可序列化。在需要执行 EJB 逻辑时,通过 processPayment 方法在运行时进行 JNDI 查找。这种方式解耦了数据状态与业务逻辑组件的生命周期,符合分布式系统的最佳实践。
立即学习“Java免费学习笔记(深入)”;
如何在反序列化后重建 EJB 连接
反序列化后的对象处于“裸”状态,没有经过容器的依赖注入。为了使用 EJB 功能,必须在对象恢复后显式地建立连接。这可以通过实现 readObject 方法或使用 @PostConstruct 风格的初始化逻辑来完成。关键在于使用唯一的标识符(如订单 ID 或用户 ID)来定位具体的业务资源,而不是尝试恢复之前的连接句柄。

import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.ObjectStreamClass;
public class SecureOrderClient extends OrderServiceClient {
public SecureOrderClient(String orderId, double amount) {
super(orderId, amount);
}
private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ois.defaultReadObject();
// 自定义反序列化逻辑:验证数据完整性
if (this.orderId == null || this.amount <= 0) {
throw new IllegalArgumentException("Invalid order data after deserialization");
}
System.out.println("Order client restored. Ready to lookup EJB.");
}
public void executeTransaction() {
// 模拟重新绑定 EJB 引用的过程
System.out.println("Re-establishing connection to Payment Gateway for Order: " + orderId);
// 实际项目中应在此处调用 lookup 并执行操作
}
}
在 SecureOrderClient 中,我们重写了 readObject 以添加额外的安全检查。虽然这里没有直接恢复 EJB 引用,但对象已经准备好在后续调用中通过 JNDI 获取新的引用。这种模式确保了即使在集群环境或服务器重启后,反序列化的数据对象也能安全地重新接入业务逻辑层,而不会保留过期的或无效的远程引用。
基于订单金额字段的序列化校验问题
在处理包含数值字段的 EJB 相关对象时,必须注意浮点数精度和空值处理对序列化结果的影响。例如,当 amount 字段为 null 或 0.0 时,序列化流会正常写入默认值,但在反序列化后若未进行非空校验,可能导致下游 EJB 调用出现算术异常或业务逻辑错误。因此,在反序列化阶段加入对关键字段的验证是必要的步骤。


















