在服务器端,我有一个ListenerManager
,它会触发Listener
的回调。使用Spring RmiServiceExporter
在客户端,我有一个由RmiProxyFactoryBean
创建的经理的代理,以及通过此代理向服务器端的经理注册的Listener
实现。
到目前为止一切顺利:ListenerManager
被赋予Listener
并调用其回调,但由于侦听器只是客户端对象的反序列化副本,因此回调在服务器上运行方,而不是客户方。
如何让Spring在服务器端为客户端侦听器生成代理,以便服务器调用的回调在客户端远程执行?当然,我不需要另一个(出口商,代理工厂)对相反的方向?
答案 0 :(得分:4)
纯RMI解决方案:客户端侦听器对象需要实现java.rmi.server.UnicastRemoteObject
。如果确实如此,并且每个方法都抛出RemoteException
,那么当它通过管理器代理传递到服务器时,一切都自动连接,服务器端代理到此监听器的方法调用是方法的远程调用在真正的客户端对象上。
这样做,但是能够包装对象以便导出而不需要特定的超类更好。我们可以使用CGLIB Enhancer将侦听器“代理”为UnicastRemoteObject
的子类,同时实现服务接口。这仍然要求目标对象实现java.rmi.Remote
并声明throws RemoteException
。
下一步是解决方案,可以导出任意对象以远程调用其方法,而无需实现Remote
或声明throws RemoteException
。我们必须将这个代理与现有的Spring基础结构集成在一起,我们可以使用RmiBasedExporter
的新实现来建模RmiServiceExporter#prepare()
的非注册表位,以导出代理的RMI存根和调用RmiClientInterceptor.doInvoke(MethodInvocation, RmiInvocationHandler)
的一部分。我们需要能够获得服务接口的导出代理实例。我们可以使用Spring对显然“导出”非RMI接口的方法对此进行建模。 Spring代理接口以生成用于调用非RMI方法的RmiInvocationWrapper
,序列化方法细节和参数,然后在RMI连接的远端调用它。
ProxyFactory
和RmiInvocationHandler
实现来代理目标对象。RmiBasedExporter
到getObjectToExport()
的新实施,并使用UnicastRemoteObject#export(obj, 0)
将其导出。rmiInvocationHandler.invoke(invocationFactory.createRemoteInvocation(invocation))
,DefaultRemoteInvocationFactory
。UndeclaredThrowableException
s。因此,我们可以使用RMI导出任意对象。这意味着我们可以在客户端使用其中一个对象作为RMI服务器端对象上的RMI方法调用的参数,并且当服务器端的反序列化存根调用了方法时,这些方法将在客户端。魔法。
答案 1 :(得分:2)
在Joe Kearney的解释之后,我创建了我的RMIUtil.java。希望没有什么可以留下的。
BTW,请参考this 对于“java.rmi.NoSuchObjectException:表中没有这样的对象”
答案 2 :(得分:0)
只需在Joe的回答中添加一些代码即可。
扩展RmiServiceExporter并获取对导出对象的访问权限:
public class RmiServiceExporter extends org.springframework.remoting.rmi.RmiServiceExporter {
private Object remoteService;
private String remoteServiceName;
@Override
public Remote getObjectToExport() {
Remote exportedObject = super.getObjectToExport();
if (getService() instanceof Remote && (
getServiceInterface() == null || exportedObject.getClass().isAssignableFrom(getServiceInterface()))) {
this.remoteService = exportedObject;
}
else {
// RMI Invokers.
ProxyFactory factory = new ProxyFactory(getServiceInterface(),
new RmiServiceInterceptor((RmiInvocationHandler) exportedObject, remoteServiceName));
this.remoteService = factory.getProxy();
}
return exportedObject;
}
public Object getRemoteService() {
return remoteService;
}
/**
* Override to get access to the serviceName
*/
@Override
public void setServiceName(String serviceName) {
this.remoteServiceName = serviceName;
super.setServiceName(serviceName);
}
}
代理中使用的拦截器(远程服务回调):
public class RmiServiceInterceptor extends RemoteInvocationBasedAccessor
implements MethodInterceptor, Serializable {
private RmiInvocationHandler invocationHandler;
private String serviceName;
public RmiServiceInterceptor(RmiInvocationHandler invocationHandler) {
this(invocationHandler, null);
}
public RmiServiceInterceptor(RmiInvocationHandler invocationHandler, String serviceName) {
this.invocationHandler = invocationHandler;
this.serviceName = serviceName;
}
/**
* {@inheritDoc}
*/
public Object invoke(MethodInvocation invocation) throws Throwable {
try {
return invocationHandler.invoke(createRemoteInvocation(invocation));
}
catch (RemoteException ex) {
throw RmiClientInterceptorUtils.convertRmiAccessException(
invocation.getMethod(), ex, RmiClientInterceptorUtils.isConnectFailure(ex),
extractServiceUrl());
}
}
/**
* Try to extract service Url from invationHandler.toString() for exception info
* @return Service Url
*/
private String extractServiceUrl() {
String toParse = invocationHandler.toString();
String url = "rmi://" + StringUtils.substringBefore(
StringUtils.substringAfter(toParse, "endpoint:["), "]");
if (serviceName != null)
url = StringUtils.substringBefore(url, ":") + "/" + serviceName;
return url;
}
}
使用此RmiServiceExporter
导出服务时,我们会发送一个rmi回调函数:
someRemoteService.someRemoteMethod(rmiServiceExporter.getRemoteService());