Spring - 通过 RMI 传递监听器

Spring - Passing Listener via RMI

基础

我有几个项目,它们实现了一个缓存服务——每个项目都有自己的缓存服务,而且每个项目都几乎与其他项目相同。我已经将现有的缓存服务压缩并缩短为一个项目,其中包含两种不同的缓存服务——一种用于基本对象,一种用于复杂对象(它扩展了基本服务)。我已经创建了 2 个项目 - 一个部署为 .war-file 和 API-project for this.

整个应用程序在 JBoss AS 7.1 "Thunder" 上运行,最新的 JDK 7.

问题

一个 class 特别需要在旧的缓存条目被删除时得到通知。为此,我使用 CleanupListeners 实现了一个通知程序。

CleanupListener.java

public interface CleanupListener extends Remote {

    public abstract boolean supports(Class<?> clazz) throws RemoteException;

    public abstract void notify(Object removedObject) throws RemoteException;

}

缓存服务中的必要实现

public void registerCleanupListener(CleanupListener listener) {
    listeners.add(listener);
}

private void fireCleanupEvent(Object removedObject) {
    for (CleanupListener cleanupListener : listeners) {
        try {
            if (cleanupListener.supports(removedObject.getClass())) {
                cleanupListener.notify(removedObject);
            }
        } catch (RemoteException re) {
            LOGGER.error("Remote Listener not available", re);
        }
    }
}

监听器通过 private final HashSet<CleanupListener>
管理 需要通知的项目使用了这个接口的实现:

@Component
public class CleanupEventListener extends UnicastRemoteObject implements CleanupListener {

    protected CleanupEventListener() throws RemoteException {
        super();
    }

    @Resource
    private ClassRequiresNotification notifyMe;

    public boolean supports(Class<?> clazz) {
        return SupportedObject.class.isAssignableFrom(clazz);
    }

    public void notify(Object removedObject) {
        notifyMe.someMethod((SupportedObject) removedObject);
    }
}

和一个注册 bean:

@Component
public class CleanupEventListenerRegistrator {

    @Resource
    private CleanupEventListener cleanupEventListener;

    @Resource
    private BasicCacheService cacheService;

    @PostConstruct
    public void init() {
        cacheService.registerCleanupListener(cleanupEventListener);
    }
}

远程服务通过SpringBean导出:

@Bean
public RmiServiceExporter basicCacheServiceExporter(BasicCacheService basicCacheService) {
    RmiServiceExporter cacheService = new RmiServiceExporter();
    cacheService.setService(basicCacheService);
    cacheService.setServiceName(BASIC_CACHE_SERVICE_NAME);
    cacheService.setServiceInterface(some.package.BasicCacheService.class);
    return cacheService;
}

并通过 XML-Config:

注册
<bean id="basicCacheService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
    <property name="lookupStubOnStartup" value="false"/>
    <property name="serviceUrl" value="basicCacheService" />
    <property name="serviceInterface" value="some.package.BasicCacheService" />
    <property name="refreshStubOnConnectFailure" value="true"/>
</bean>

异常

所以,每当我尝试调用预期的方法时,我都会得到两个异常之一:

要么我得到一个 NoSuchObjectException(罕见),要么更常见的是 ClassNotFoundException 包裹在 UnmarshalException

我做了什么

我已经查阅了 JavaDocs,大量搜索并在这里筛选了几个问题,但无济于事。我发现一些信息表明标准 属性 rmi.codebase 设置为 true,这可能需要重写一些代码,但是我们已经在项目的另一个地方使用 RMI,它在那里工作得很好。添加 SecurityManager 会破坏我们应用程序的其余部分(由于外部先决条件)并调用 NoSuchObjectException。 到目前为止,Whosebug 中的答案已经产生 "this is a problem with the codebase"、"you need to install a securityManager"、"the classes have to have the same name and be in the same package",当然还有 "it's the codebase"。我提到这个问题两次,因为它确实经常出现,但没有提到如何处理代码库问题。

附加信息

我不确定这是否重要,但这里有更多信息: cacheservice-project在java代码中配置,使用该服务的项目在XML.
中配置 Listener-Interface 没有通过 Annotations 或 XML-Beans 导出,因为几个帖子(包括至少一个这里)暗示这将导出 class,但只在服务器端继续监听,这不是我想要的。
我还尝试使 CleanupListener 成为一个抽象 class,它本身扩展了 UnicastRemoteObject,但这也没有成功——事实上,它比我现在拥有的更糟糕。
我正在 eclipse 中编写和测试应用程序。
组装和发布到服务器是通过命令行中的 gradle 完成的。

问题

有谁知道这里的问题是什么,以及如何实际解决它?当我所做的一切要么 运行 成为异常,要么是 "yeah that's a codebase error",这就变得非常令人恼火,因为两者都无济于事。

如有任何帮助,我们将不胜感激。

实际答案

好的,这既令人尴尬又令人宽慰。首先...

好消息

如上所述的侦听器接口有效。它在服务器端注册,并在调用时向客户端发出通知,然后客户端可能会采取相应的行动。所以,如果您一直在寻找整个 "passing a listener" 问题的答案,那么您就有了。

坏消息

然而,我对某些事实一无所知。除其他事项外,术语 codebase 实际涵盖的内容。在我的特殊情况下,问题是缓存服务本身不依赖于其他项目,它应该保留(自定义)类。所以从技术上讲,代码库 实际上 问题 - 由于 build.gradle.

中缺少依赖项

我希望我不会在这个问题上浪费任何人的时间,如果是这样,请接受我的道歉,希望这个问题有时能对其他人有所帮助。