Spring RMI 不需要输出的接口继承自Remote(这也是Spring一直坚持的原则之一: 业务逻辑不应该同远程逻辑一起设计)。但是这是如何做的呢, 答案是代理。以下是一个简化版的例子程序,用于模拟Spring的实现。
首先是用于发布的接口及实现:
MockProduct.java
public interface MockProduct {
String getDescription();
}
MockProductRemote.java
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MockProductRemote extends Remote {
String getDescription() throws RemoteException;
}
MockProductImpl.java
public class MockProductImpl implements MockProduct {
public String getDescription() {
return "Mock Product";
}
}
MockProductRemoteImpl.java
import java.rmi.RemoteException;
public class MockProductRemoteImpl implements MockProductRemote {
public String getDescription() throws RemoteException {
return "Mock Product Remote";
}
}
接下来是用于输出RMI服务的类MockRmiServiceExporter.java
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;
import org.springframework.aop.framework.ProxyFactory;
public class MockRmiServiceExporter {
//
private Object service;
private String serviceName;
private Class<?> serviceInterface;
//
private Remote exportedObject;
public Object invoke(MockRemoteInvocation invocation, Object wrappedObject) throws RemoteException {
return invocation.invoke(wrappedObject);
}
public void afterPropertiesSet() throws Throwable {
Registry reg = getRegistry();
this.exportedObject = getObjectToExport();
UnicastRemoteObject.exportObject(this.exportedObject);
reg.rebind(this.serviceName, this.exportedObject);
}
public Object getService() {
return service;
}
public void setService(Object service) {
this.service = service;
}
public String getServiceName() {
return serviceName;
}
public void setServiceName(String serviceName) {
this.serviceName = serviceName;
}
public Class<?> getServiceInterface() {
return serviceInterface;
}
public void setServiceInterface(Class<?> serviceInterface) {
this.serviceInterface = serviceInterface;
}
private Registry getRegistry() throws RemoteException {
try {
Registry reg = LocateRegistry.getRegistry(Registry.REGISTRY_PORT);
reg.list(); // Test it
return reg;
} catch (RemoteException ex) {
return LocateRegistry.createRegistry(Registry.REGISTRY_PORT);
}
}
private Remote getObjectToExport() {
// determine remote object
if (getService() instanceof Remote &&
((getServiceInterface() == null) || Remote.class.isAssignableFrom(getServiceInterface()))) {
// conventional RMI service
return (Remote) getService();
} else {
// RMI invoker
return new MockRmiInvocationWrapper(getProxyForService(), this);
}
}
protected Object getProxyForService() {
ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.addInterface(getServiceInterface());
proxyFactory.setTarget(getService());
return proxyFactory.getProxy();
}
}
在这个段代码中是用LocateRegistry类的相关方法代替了启动rmiregistry。 在getObjectToExport方法中,如果getService()和getServiceInterface()都没有实现Remote接口,那么实际发布的是MockRmiInvocationWrapper; 相反,如果getService()和getServiceInterface()都实现了Remote接口, 那么实际发布的就是这个Remote接口本身, 这意味着客户端可以不用使用Spring的RMI客户端实现,而是使用传统RMI的方式。
为了保证在不同版本的jdk下都能正确执行,需要手动用rmic为MockRmiInvocationWrapper生成一个stub(但是对于待发布的接口来说,便不再需要stub)。如果使用UnicastRemoteObject.exportObject(this.exportedObject, 0)方法来发布对象,那么连这个stub也可以不必生成。顺便说一下,在Spring的二进制发布包spring-context.jar中有一个RmiInvocationWrapper_Stub.class便是完成类似的功能。
再接下来是MockRmiInvocationWrapper.java的代码, 它实现了Remote接口。
import java.rmi.RemoteException;
public class MockRmiInvocationWrapper implements MockRmiInvocationHandler {
private Object wrappedObject;
private MockRmiServiceExporter rmiExporter;
public MockRmiInvocationWrapper(Object wrappedObject, MockRmiServiceExporter rmiExporter) {
this.wrappedObject = wrappedObject;
this.rmiExporter = rmiExporter;
}
public Object invoke(MockRemoteInvocation invocation) throws RemoteException {
return this.rmiExporter.invoke(invocation, this.wrappedObject);
}
}
MockRmiInvocationHandler.java
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface MockRmiInvocationHandler extends Remote {
Object invoke(MockRemoteInvocation invocation) throws RemoteException;
}
MockRemoteInvocation.java
import java.io.Serializable;
import java.lang.reflect.Method;
import java.rmi.RemoteException;
import org.aopalliance.intercept.MethodInvocation;
public class MockRemoteInvocation implements Serializable{
//
private static final long serialVersionUID = 1044994565810431680L;
//
private String methodName;
private Class<?>[] parameterTypes;
private Object[] arguments;
public MockRemoteInvocation(MethodInvocation methodInvocation) {
this.methodName = methodInvocation.getMethod().getName();
this.parameterTypes = methodInvocation.getMethod().getParameterTypes();
this.arguments = methodInvocation.getArguments();
}
public Object invoke(Object targetObject) throws RemoteException {
try {
Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
return method.invoke(targetObject, this.arguments);
} catch (Exception e) {
throw new RemoteException(e.toString());
}
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Object[] getArguments() {
return arguments;
}
public void setArguments(Object[] arguments) {
this.arguments = arguments;
}
}
再接下来是客户端的相关类。在MockRmiProxyFactoryBean中,它使用MethodInterceptor拦截了所有对于导出接口方法的调用并转发给相应的stub。如果这个stub是实现了MockRmiInvocationHandler(比如MockRmiInvocationWrapper), 那么会把所有的调用转发给MockRmiInvocationHandler的invoke方法,同时构造一个MockRemoteInvocation。此后就是通过MockRmiInvocationWrapper_Stub进行远程调用了。
服务器端接收到请求之后,在MockRmiInvocationWrapper类中又把这个请求转发到this.rmiExporter.invoke(invocation, this.wrappedObject)。在MockRmiServiceExporter的invoke方法中,再次转发给MockRemoteInvocation的invoke方法, 最终在MockRemoteInvocation.inovke方法中调用了targetObject上的相关方法。顺便说一下,由于MockRemoteInvocation对象是从客户端通过序列化传递到服务器端的, 而且在这个对象中保存了客户端调用的方法名及参数列表,所以在这个对象中能最终决定应该调用targetObject上的哪个方法。
MockRmiProxyFactoryBean.java
import java.lang.reflect.Method;
import java.rmi.Naming;
import java.rmi.Remote;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.factory.FactoryBean;
public class MockRmiProxyFactoryBean implements FactoryBean, MethodInterceptor {
//
private Object serviceProxy;
//
private String serviceUrl;
private Class<?> serviceInterface;
public Object getObject() {
return serviceProxy;
}
@SuppressWarnings("unchecked")
public Class getObjectType() {
return getServiceInterface();
}
public boolean isSingleton() {
return true;
}
protected Remote getStub() throws Throwable {
Remote stub = Naming.lookup(getServiceUrl());
return stub;
}
public Object invoke(MethodInvocation invocation) throws Throwable {
Remote stub = null;
try {
stub = getStub();
} catch (Throwable ex) {
throw ex;
}
try {
return doInvoke(invocation, stub);
} catch (Exception ex) {
throw ex;
}
}
protected Object doInvoke(MethodInvocation mi, Remote stub) throws Throwable {
if (stub instanceof MockRmiInvocationHandler) {
// RMI invoker
try {
return doInvoke(mi, (MockRmiInvocationHandler)stub);
} catch (Exception ex) {
throw ex;
}
} else {
// traditional RMI stub
try {
Method method = mi.getMethod();
if (method.getDeclaringClass().isInstance(stub)) {
// directly implemented
return method.invoke(stub, mi.getArguments());
} else {
// not directly implemented
Method stubMethod = stub.getClass().getMethod(method.getName(), method.getParameterTypes());
return stubMethod.invoke(stub, mi.getArguments());
}
} catch (Exception ex) {
throw ex;
}
}
}
protected Object doInvoke(MethodInvocation mi, MockRmiInvocationHandler mrih) throws Throwable {
return mrih.invoke(createRemoteInvocation(mi));
}
protected MockRemoteInvocation createRemoteInvocation(MethodInvocation mi) throws Throwable {
return new MockRemoteInvocation(mi);
}
public void afterPropertiesSet() {
serviceProxy = ProxyFactory.getProxy(getServiceInterface(), this);
}
public String getServiceUrl() {
return serviceUrl;
}
public void setServiceUrl(String serviceUrl) {
this.serviceUrl = serviceUrl;
}
public Class<?> getServiceInterface() {
return serviceInterface;
}
public void setServiceInterface(Class<?> serviceInterface) {
this.serviceInterface = serviceInterface;
}
}
以上的例子可以看成Spring实现的一个简化版本,其中去掉了复杂的异常处理,一些类的继承关系等。但是保留了基本的控制逻辑,有助于加深理解。
最后是编写两个测试类,检验一下工作成果了。执行前需要生成MockRmiInvocationWrapper的存根类。另外由于MockProductRemote继承了Remote接口,因此不是以MockRmiInvocationWrapper发布的,所以还要生成MockProductRemoteImpl的存根类。
MockRmiServer.java
public class MockRmiServer {
public static void main(String args[]) {
try {
//
System.out.println("starting mock server...");
MockProduct mp = new MockProductImpl();
MockRmiServiceExporter rmiExporter1 = new MockRmiServiceExporter();
rmiExporter1.setService(mp);
rmiExporter1.setServiceInterface(MockProduct.class);
rmiExporter1.setServiceName("mockProduct");
rmiExporter1.afterPropertiesSet();
// rmic MockProductRemoteImpl
MockProductRemote mpr = new MockProductRemoteImpl();
MockRmiServiceExporter rmiExporter2 = new MockRmiServiceExporter();
rmiExporter2.setService(mpr);
rmiExporter2.setServiceInterface(MockProductRemote.class);
rmiExporter2.setServiceName("mockProductRemote");
rmiExporter2.afterPropertiesSet();
System.out.println("done");
} catch (Throwable e) {
e.printStackTrace();
}
}
}
MockRmiClient.java
public class MockRmiClient {
public static void main(String args[]) {
try {
//
System.out.println("starting mock client...");
MockRmiProxyFactoryBean factoryBean1 = new MockRmiProxyFactoryBean();
factoryBean1.setServiceUrl("rmi://127.0.0.1:1099/mockProduct");
factoryBean1.setServiceInterface(MockProduct.class);
factoryBean1.afterPropertiesSet();
MockProduct mp = (MockProduct)factoryBean1.getObject();
System.out.println("got mock product: " + mp.getDescription());
//
MockRmiProxyFactoryBean factoryBean2 = new MockRmiProxyFactoryBean();
factoryBean2.setServiceUrl("rmi://127.0.0.1:1099/mockProductRemote");
factoryBean2.setServiceInterface(MockProductRemote.class);
factoryBean2.afterPropertiesSet();
MockProductRemote mpr = (MockProductRemote)factoryBean2.getObject();
System.out.println("got mock product remote: " + mpr.getDescription());
} catch (Throwable e) {
e.printStackTrace();
}
}
}