【发布时间】:2014-03-10 11:38:42
【问题描述】:
如果我通过 RMI 使用 RmiServiceExporter 导出服务,服务调用的返回值将被序列化并通过线路发送到客户端。但是除非我有一个简单的服务来返回简单的数据,否则这不是很有用。
我也希望能够在这个返回值上调用方法,并让它们在服务器上运行,而不是在客户端上运行。本质上,我想要在客户端创建的返回值的代理。有没有办法使用RmiServiceExporter(或 Spring 支持的其他远程处理选项)来做到这一点?
我尝试实现一些返回值的自动包装,这就是我想出的:
在服务器端:
class RmiReturnValueStubbingExporter extends RmiServiceExporter {
/* we can't use the Spring implementation of RmiInvocationHandler
* because it's package protected */
private class RmiReturnValueInvocationWrapper implements RmiInvocationHandler {
private final Object wrappedObject;
private RmiReturnValueInvocationWrapper(Class interf, Object wrappedObject) {
this.wrappedObject = createProxyFor(interf, wrappedObject);
}
@Override
public String getTargetInterfaceName() throws RemoteException {
return null;
}
@Override
public Object invoke(RemoteInvocation invocation) throws
RemoteException, NoSuchMethodException, IllegalAccessException,
InvocationTargetException {
return RmiReturnValueStubbingExporter.this.invoke(
invocation, this.wrappedObject);
}
}
@Override
protected Object invoke(RemoteInvocation invocation, Object targetObject)
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
Object ret = maybeWrap(super.invoke(invocation, targetObject));
if (ret instanceof Remote) {
try {
UnicastRemoteObject.exportObject((Remote) ret, 0);
} catch (RemoteException e) {
throw new InvocationTargetException(e);
}
}
return ret;
}
private Object maybeWrap(Object superValue) {
if (superValue instanceof OntologyTerm) {
return new RmiReturnValueInvocationWrapper(
OntologyTerm.class, superValue);
} else if (superValue instanceof List) {
return new RmiReturnValueInvocationWrapper(
List.class, superValue);
} else {
return superValue;
}
}
private Object createProxyFor(Class interf, Object object) {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addInterface(interf);
proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
proxyFactory.setTarget(object);
// don't make opaque, on the client we need to know the interfaces implemented
return proxyFactory.getProxy(getBeanClassLoader());
}
}
在客户端:
class RmiStubReturnValueProxyFactoryBean extends RmiProxyFactoryBean {
@Override
protected Object doInvoke(MethodInvocation methodInvocation,
RmiInvocationHandler invocationHandler) {
def superValue = super.doInvoke(methodInvocation, invocationHandler)
if (superValue instanceof java.lang.reflect.Proxy &&
java.lang.reflect.Proxy.getInvocationHandler(superValue)
instanceof RemoteObjectInvocationHandler) {
RmiInvocationHandler rih = superValue
def proxiedInterfaces = rih.invoke(
new RemoteInvocation('getProxiedInterfaces',
[] as Class[], [] as Object[]))
def clientProxy = new ProxyFactory(proxiedInterfaces)
clientProxy.addAdvice({ MethodInvocation invoc ->
doInvoke(invoc, rih)
} as MethodInterceptor)
clientProxy.getProxy beanClassLoader
} else {
superValue
}
}
}
这似乎适用于两层包装;在这种情况下,服务返回 List<OntologyTerm>。我怀疑这有很多问题,首先是调用UnicastRemoteObject.exportObject 而没有回收任何东西。此处可能还缺少的另一件事是处理这些存根的传递,这些存根将客户端作为参数传递给服务。因为参数是在客户端创建的 Proxy 对象(包装 RMI 存根),我猜至少服务器必须从客户端加载类,这是我想避免的。
我的问题是,是否已经实现了类似的东西(不一定使用 RMI),我可以在其中轻松指定哪些类型作为值返回并序列化,以及哪些类型的返回值应该被存根。
【问题讨论】:
-
这就是 EJB 用于
@Remote接口的用途。您正在尝试重新发明轮子。 -
@M.Deinum 我希望我在重新发明轮子,但我不知道 EJB 是否是正确的答案。我的服务器是一个在 tomcat 中运行的 webapp(没有 Java EE 堆栈),客户端应用程序应该是一个可以从其他通用 Java SE 应用程序中使用的库,这些应用程序肯定不会在应用程序服务器上运行。
-
所以基本上你想要 EJB 可以给你的东西,但不能使用 EJB,所以你正在拼凑一些可能有用的东西。默认的
RmiServiceExporter(以及所有其他远程解决方案)仅序列化方法调用并序列化该调用的结果。如果这不是您想要的,请不要使用 Springs 远程处理技术,而是使用普通 RMI 来获得正常的远程处理行为。 -
@M.Deinum 我可以这样做,但如果有替代方案我不需要使用 RMI 特定接口和逻辑破坏我的代码,我会非常喜欢。这就是首先吸引我
RmiServiceExporter的原因。