如何通过RMI传递事务对象?Java跨进程调用遇类加载异常
问题分析与解决方案
你遇到的核心问题是RMI传递匿名内部类时的序列化与类加载失败:异常里的ClassNotFoundException: com.mycompany.backend.repository.FileRepositoryImpl$1,这个$1就是你定义的匿名Command内部类。RMI在传递对象时,要求两端(客户端和服务端)都有对应的类定义,而匿名内部类不仅会隐式持有外部类FileRepositoryImpl的引用(这个外部类是远程对象,本身不适合序列化),而且服务端的类路径里也没有这个匿名类的字节码,再加上RMI类加载器被禁用,就导致了反序列化失败。
下面是可行的解决方案,按优先级排序:
1. 将命令对象改为静态内部类/顶级类
匿名内部类是非静态的,会绑定外部类实例,这会带来额外的序列化负担。改成静态内部类或独立的顶级类,既避免持有外部类引用,也能让类名清晰,方便在服务端类路径中部署。
示例代码(静态内部类版本):
public class FileRepositoryImpl extends UnicastRemoteObject implements FileRepository { // 静态内部类:不持有外部类引用,可独立序列化 private static class FetchFileCommand implements Command<byte[]> { private final String filePath; public FetchFileCommand(String filePath) { this.filePath = filePath; } @Override public byte[] execute(jetbrains.exodus.entitystore.StoreTransaction txn) { Entity entity = txn.findWithBlob(Constants.ENTITYSTORE_FILE, filePath).getLast(); if (entity == null) { return null; } // 使用try-with-resources确保流关闭,避免资源泄漏 try (InputStream blobStream = entity.getBlob(filePath)) { return ByteStreams.toByteArray(blobStream); } catch (IOException e) { // 可根据业务需求处理异常,比如抛出RuntimeException或返回null return null; } } } @Override public byte[] get(String appId, String filePath) throws RemoteException, NotBoundException { Registry registry = LocateRegistry.getRegistry(); XodusRepository repository = (XodusRepository) registry.lookup(XodusRepository.class.getName()); // 直接传递静态内部类实例,无需用数组捕获结果 return repository.transact(appId, true, new FetchFileCommand(filePath)); } }
2. 确保Command接口实现Serializable
RMI传递的所有对象(包括接口的实现类)都必须实现Serializable接口,否则无法序列化传递。修改你的Command接口:
// 继承Serializable,确保实现类可被RMI序列化 public interface Command<T> extends Serializable { T execute(jetbrains.exodus.entitystore.StoreTransaction txn); }
注意:StoreTransaction是服务端本地对象,不需要序列化——execute方法是在服务端执行的,服务端会传入自己的事务实例,客户端只需要传递"命令逻辑"即可。
3. 修复服务端类加载与代码逻辑问题
- 共享类路径:把
Command接口、FetchFileCommand类的字节码,放到RMI服务端和客户端的共同类路径中,这样就不需要依赖RMI动态类加载(也就避开了安全管理器的限制)。 - 修复服务端transact方法的空指针:你当前的
AtomicReference<T> result = null;会导致空指针,需要初始化:
@Override public <T> T transact(String appId, boolean isReadOnly, Command<T> command) throws NotBoundException, RemoteException { AtomicReference<T> result = new AtomicReference<>(); // 初始化引用 manager.transactPersistentEntityStore(xodusRoot, appId, isReadOnly, txn -> { result.set(command.execute(txn)); }); return result.get(); }
核心原理总结
RMI的本质是序列化传递对象+远程执行方法,传递的对象必须满足:
- 实现
Serializable接口(或Remote接口) - 两端类路径中都有该类的完整定义(包括父类、接口)
- 避免传递绑定外部类的非静态内部类(会引入不必要的序列化依赖)
通过上面的改造,你就能安全地通过RMI传递命令对象,在服务端执行事务逻辑并返回结果。
内容的提问来源于stack exchange,提问作者quarks
相关产品推荐
相关产品推荐

