1.Spring除了使用基於HTTP協議的遠程調用方案,還爲開發者提供了基於RMI機制的遠程調用方法,RMI遠程調用網絡通信實現是基於TCP/IP協議完成的,而不是通過HTTP協議。
在Spring RMI實現中,集成了標準的RMI-JRIM解決方案,該方案是java虛擬機實現的一部分,它使用java序列化來完成對象的傳輸,是一個java到java環境的分佈式處理技術,不涉及異構平臺的處理。
2.RMI客戶端配置:
和基於HTTP協議的遠程調用類似,RMI遠程調用客戶端也需要進行類似如下的配置:
- <bean id=”rmiProxy” class=”org.springframework.remoting.rmi.RmiProxyFactoryBean”>
- <property name=”serviceUrl”>
- <value>rmi://hostAddress:1099/serviceUrl</value>
- </property>
- <property name=”serviceInterface”>
- <value>遠程調用接口</value>
- </property>
- </bean>
- <bean id=”rmiClient” class=”RMI遠程調用客戶端類全路徑”>
- <property name=”serviceInterface”>
- <ref bean=”rmiProxy”/>
- </property>
- </bean>
注意:上面的配置中serviceUrl必須和服務端的遠程調用提供者的id一致,另外,serviceUrl中使用的是rmi協議,默認端口是1099.
RmiProxyFactoryBean的主要功能是對RMI客戶端封裝,生成代理對象,查詢得到RMI的stub對象,並通過這個stub對象發起相應的RMI遠程調用請求。其源碼如下:
- public class RmiProxyFactoryBean extends RmiClientInterceptor implements FactoryBean<Object>, BeanClassLoaderAware {
- //遠程調用代理對象
- private Object serviceProxy;
- //Spring IoC容器完成依賴注入後的回調方法
- public void afterPropertiesSet() {
- //調用父類RmiClientInterceptor的回調方法
- super.afterPropertiesSet();
- //獲取客戶端配置的遠程調用接口
- if (getServiceInterface() == null) {
- throw new IllegalArgumentException("Property 'serviceInterface' is required");
- }
- //創建遠程調用代理對象併爲代理對象設置攔截器。注意第二個參數this,因爲
- //RmiProxyFactoryBean繼承RmiClientInterceptor,因此其也是攔截器
- this.serviceProxy = new ProxyFactory(getServiceInterface(), this).getProxy(getBeanClassLoader());
- }
- //Spring IoC容器獲取被管理對象的接口方法,獲取遠程調用代理
- public Object getObject() {
- return this.serviceProxy;
- }
- public Class<?> getObjectType() {
- return getServiceInterface();
- }
- public boolean isSingleton() {
- return true;
- }
- }
通過對上面RmiProxyFactoryBean源碼分析中,我們看到在創建遠程調用代理對象的時候需要設置攔截器,因爲我們繼續分析遠程調用客戶端攔截器RmiClientInterceptor。
4.RmiClientInterceptor封裝RMI客戶端:
RmiClientInterceptor對客戶端的遠程調用進行攔截,具體的生成遠程調用代理對象、查找遠程調用stub、以及通過RMI stub向服務端發起遠程調用請求的具體實現都由RMI客戶端攔截器實現,其源碼如下:
- public class RmiClientInterceptor extends RemoteInvocationBasedAccessor
- implements MethodInterceptor {
- //在Spring啓動時查找遠程調用stub
- private boolean lookupStubOnStartup = true;
- //對查找到或使用過的遠程調用stub進行緩存
- private boolean cacheStub = true;
- //當連接失敗是是否刷新遠程調用stub
- private boolean refreshStubOnConnectFailure = false;
- //RMI客戶端socket工廠
- private RMIClientSocketFactory registryClientSocketFactory;
- //緩存的遠程調用stub
- private Remote cachedStub;
- //創建遠程調用stub監控器
- private final Object stubMonitor = new Object();
- //設置是否啓動時查找RMI stub
- public void setLookupStubOnStartup(boolean lookupStubOnStartup) {
- this.lookupStubOnStartup = lookupStubOnStartup;
- }
- //設置是否緩存以查找的RMI stub
- public void setCacheStub(boolean cacheStub) {
- this.cacheStub = cacheStub;
- }
- //設置當連接失敗時,是否刷新RMI stub
- public void setRefreshStubOnConnectFailure(boolean refreshStubOnConnectFailure) {
- this.refreshStubOnConnectFailure = refreshStubOnConnectFailure;
- }
- //設置客戶端socket工廠
- public void setRegistryClientSocketFactory(RMIClientSocketFactory registryClientSocketFactory) {
- this.registryClientSocketFactory = registryClientSocketFactory;
- }
- //Spring IoC容器回調方法,由子類RmiProxyFactoryBean回調方法調用
- public void afterPropertiesSet() {
- //調用父類RemoteInvocationBasedAccessor的回調方法
- super.afterPropertiesSet();
- prepare();
- }
- //初始化RMI客戶端
- public void prepare() throws RemoteLookupFailureException {
- //如果設置了啓動時查找RMI stub
- if (this.lookupStubOnStartup) {
- //查找RMI stub
- Remote remoteObj = lookupStub();
- if (logger.isDebugEnabled()) {
- //如果查找到的RMI stub是RmiInvocationHandler類型
- if (remoteObj instanceof RmiInvocationHandler) {
- logger.debug("RMI stub [" + getServiceUrl() + "] is an RMI invoker");
- }
- //如果獲取到客戶端配置的serviceInterface不爲null
- else if (getServiceInterface() != null) {
- //判斷客戶端配置的serviceInterface是否是RMI stub實例
- boolean isImpl = getServiceInterface().isInstance(remoteObj);
- logger.debug("Using service interface [" + getServiceInterface().getName() +
- "] for RMI stub [" + getServiceUrl() + "] - " +
- (!isImpl ? "not " : "") + "directly implemented");
- }
- }
- //如果設置了緩存RMI stub,將緩存的stub設置爲查找到的RMI stub
- if (this.cacheStub) {
- this.cachedStub = remoteObj;
- }
- }
- }
- //查找RMI stub
- protected Remote lookupStub() throws RemoteLookupFailureException {
- try {
- Remote stub = null;
- //如果設置了客戶端socket工廠
- if (this.registryClientSocketFactory != null) {
- //獲取並解析客戶端配置的serviceUrl
- URL url = new URL(null, getServiceUrl(), new DummyURLStreamHandler());
- //獲取客戶端配置的serviceUrl協議
- String protocol = url.getProtocol();
- //如果客戶端配置的serviceUrl中協議不爲null且不是rmi
- if (protocol != null && !"rmi".equals(protocol)) {
- throw new MalformedURLException("Invalid URL scheme '" + protocol + "'");
- }
- //獲取客戶端配置的serviceUrl中的主機地址
- String host = url.getHost();
- //獲取客戶端配置的serviceUrl中的端口
- int port = url.getPort();
- //獲取客戶端配置的serviceUrl中請求路徑
- String name = url.getPath();
- //如果請求路徑不爲null,且請求路徑以”/”開頭,則去掉”/”
- if (name != null && name.startsWith("/")) {
- name = name.substring(1);
- }
- //根據客戶端配置的serviceUrl信息和客戶端socket工廠創建遠程對
- //象引用
- Registry registry = LocateRegistry.getRegistry(host, port, this.registryClientSocketFactory);
- //通過遠程對象引用查找指定RMI請求的RMI stub
- stub = registry.lookup(name);
- }
- //如果客戶端配置的serviceUrl中協議爲null或者是rmi
- else {
- //直接通過RMI標準API查找客戶端配置的serviceUrl的RMI stub
- stub = Naming.lookup(getServiceUrl());
- }
- if (logger.isDebugEnabled()) {
- logger.debug("Located RMI stub with URL [" + getServiceUrl() + "]");
- }
- return stub;
- }
- //對查找RMI stub過程中異常處理
- catch (MalformedURLException ex) {
- throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", ex);
- }
- catch (NotBoundException ex) {
- throw new RemoteLookupFailureException(
- "Could not find RMI service [" + getServiceUrl() + "] in RMI registry", ex);
- }
- catch (RemoteException ex) {
- throw new RemoteLookupFailureException("Lookup of RMI stub failed", ex);
- }
- }
- //獲取RMI stub
- protected Remote getStub() throws RemoteLookupFailureException {
- //如果沒有配置緩存RMI stub,或者設置了啓動時查找RMI stub或當連接失敗時
- //不刷新RMI stub
- if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) {
- //如果緩存的RMI stub不爲null,則直接返回,否則,查找RMI stub
- return (this.cachedStub != null ? this.cachedStub : lookupStub());
- }
- //如果設置了緩存RMI stub,且設置了啓動時查找RMI stub或者當連接失敗時刷新
- //RMI stub
- else {
- //線程同步
- synchronized (this.stubMonitor) {
- //如果緩存的RMI stub爲null
- if (this.cachedStub == null) {
- //則將查找的RMI stub緩存
- this.cachedStub = lookupStub();
- }
- //返回緩存的RMI stub
- return this.cachedStub;
- }
- }
- }
- //攔截器對客戶端遠程調用方法的攔截入口
- public Object invoke(MethodInvocation invocation) throws Throwable {
- //獲取RMI stub
- Remote stub = getStub();
- try {
- //攔截客戶端遠程調用方法
- return doInvoke(invocation, stub);
- }
- catch (RemoteConnectFailureException ex) {
- return handleRemoteConnectFailure(invocation, ex);
- }
- catch (RemoteException ex) {
- if (isConnectFailure(ex)) {
- return handleRemoteConnectFailure(invocation, ex);
- }
- else {
- throw ex;
- }
- }
- }
- //判斷是否連接失敗
- protected boolean isConnectFailure(RemoteException ex) {
- return RmiClientInterceptorUtils.isConnectFailure(ex);
- }
- //處理遠程連接失敗
- private Object handleRemoteConnectFailure(MethodInvocation invocation, Exception ex) throws Throwable {
- //如果設置了當連接失敗時,刷新RMI stub
- if (this.refreshStubOnConnectFailure) {
- String msg = "Could not connect to RMI service [" + getServiceUrl() + "] - retrying";
- if (logger.isDebugEnabled()) {
- logger.warn(msg, ex);
- }
- else if (logger.isWarnEnabled()) {
- logger.warn(msg);
- }
- //刷新查找遠程調用stub
- return refreshAndRetry(invocation);
- }
- else {
- throw ex;
- }
- }
- //刷新RMI stub
- protected Object refreshAndRetry(MethodInvocation invocation) throws Throwable {
- Remote freshStub = null;
- //線程同步
- synchronized (this.stubMonitor) {
- this.cachedStub = null;
- //查找RMI stub
- freshStub = lookupStub();
- //如果設置了緩存RMI stub
- if (this.cacheStub) {
- //將刷新查找的RMI stub緩存
- this.cachedStub = freshStub;
- }
- }
- return doInvoke(invocation, freshStub);
- }
- //具體RMI調用的地方
- protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
- //如果RMI stub是RmiInvocationHandler類型
- if (stub instanceof RmiInvocationHandler) {
- //調用RmiInvocationHandler的RMI
- try {
- return doInvoke(invocation, (RmiInvocationHandler) stub);
- }
- catch (RemoteException ex) {
- throw RmiClientInterceptorUtils.convertRmiAccessException(
- invocation.getMethod(), ex, isConnectFailure(ex), getServiceUrl());
- }
- catch (InvocationTargetException ex) {
- Throwable exToThrow = ex.getTargetException();
- RemoteInvocationUtils.fillInClientStackTraceIfPossible(exToThrow);
- throw exToThrow;
- }
- catch (Throwable ex) {
- throw new RemoteInvocationFailureException("Invocation of method [" + invocation.getMethod() +
- "] failed in RMI service [" + getServiceUrl() + "]", ex);
- }
- }
- //如果RMI stub不是RmiInvocationHandler類型
- else {
- //使用傳統的RMI調用方式
- try {
- return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, stub);
- }
- catch (InvocationTargetException ex) {
- Throwable targetEx = ex.getTargetException();
- if (targetEx instanceof RemoteException) {
- RemoteException rex = (RemoteException) targetEx;
- throw RmiClientInterceptorUtils.convertRmiAccessException(
- invocation.getMethod(), rex, isConnectFailure(rex), getServiceUrl());
- }
- else {
- throw targetEx;
- }
- }
- }
- }
- //調用RmiInvocationHandler的RMI
- protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
- throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- //如果客戶端遠程調用請求是toString()方法
- if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
- return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";
- }
- //使用RmiInvocationHandler處理RMI調用
- return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
- }
- }
通過上面對RmiClientInterceptor源碼分析,我們看到Spring對RMI遠程調用使用以下兩種方式:
(1).RMI調用器方式:
這種方式和Spring HTTP調用器非常類似,即使用RemoteInvocation來封裝調用目標對象、目標方法、參數類型等信息,RMI服務器端接收到RMI請求之後直接調用目標對象的匹配方法。
(2).傳統RMI調用方式:
使用JDK的反射機制,直接調用遠程調用stub的方法。
5.RMI的服務端配置:
在Spring RMI服務端需要進行類似如下的配置:
- <bean id=”rmiService” class=”org.springframework.remoting.rmi.RmiServiceExporter”>
- <property name=”service”>
- <ref bean=”RMI服務端對象”/>
- </property>
- <property name=”serviceInterface”>
- <value>RMI服務接口</value>
- </property>
- <property name=”serviceName”>
- <value>RMI服務導出名稱</value>
- </property>
- <property name=”registerPort”>
- <value>1099</value>
- </property>
- </bean>
RMI中,基於TCP/IP協議,而不是HTTP協議來實現底層網絡通信,由於RMI的網絡通信已由Java RMI實現,所以這裏不再使用Spring MVC的DispatcherServlet來轉發客戶端配置的遠程調用請求URL,只需要指定RMI的TCP/.IP監聽端口和服務導出的名稱即可。
6.RmiServiceExporter導出RMI遠程調用對象:
RmiServiceExporter主要功能是將服務端遠程對象提供的服務導出供客戶端請求調用,同時將導出的遠程對象和註冊器綁定起來供客戶端查詢,其主要源碼如下:
- public class RmiServiceExporter extends RmiBasedExporter implements InitializingBean, DisposableBean {
- //導出的RMI服務名稱
- private String serviceName;
- //RMI服務端口
- private int servicePort = 0;
- //RMI客戶端socket工廠
- private RMIClientSocketFactory clientSocketFactory;
- //RMI服務端socket工廠
- private RMIServerSocketFactory serverSocketFactory;
- //註冊器
- private Registry registry;
- //註冊主機
- private String registryHost;
- //註冊端口
- private int registryPort = Registry.REGISTRY_PORT;
- //客戶端註冊socket工廠
- private RMIClientSocketFactory registryClientSocketFactory;
- //服務端註冊socket工廠
- private RMIServerSocketFactory registryServerSocketFactory;
- //總是創建時註冊
- private boolean alwaysCreateRegistry = false;
- //替換已有的綁定
- private boolean replaceExistingBinding = true;
- //導出的遠程對象
- private Remote exportedObject;
- //創建註冊
- private boolean createdRegistry = false;
- //注入服務端配置的RMI導出服務名稱,格式爲:“rmi://host:post/NAME”
- public void setServiceName(String serviceName) {
- this.serviceName = serviceName;
- }
- //設置服務端口
- public void setServicePort(int servicePort) {
- this.servicePort = servicePort;
- }
- //設置RMI客戶端socket工廠
- public void setClientSocketFactory(RMIClientSocketFactory clientSocketFactory) {
- this.clientSocketFactory = clientSocketFactory;
- }
- //設置RMI服務端socket工廠
- public void setServerSocketFactory(RMIServerSocketFactory serverSocketFactory) {
- this.serverSocketFactory = serverSocketFactory;
- }
- //設置RMI註冊器
- public void setRegistry(Registry registry) {
- this.registry = registry;
- }
- //設置RMI註冊主機
- public void setRegistryHost(String registryHost) {
- this.registryHost = registryHost;
- }
- //設置RMI註冊端口
- public void setRegistryPort(int registryPort) {
- this.registryPort = registryPort;
- }
- //設置用於註冊的RMI客戶端socket工廠
- public void setRegistryClientSocketFactory(RMIClientSocketFactory registryClientSocketFactory) {
- this.registryClientSocketFactory = registryClientSocketFactory;
- }
- //設置用於註冊的RMI服務端socket工廠
- public void setRegistryServerSocketFactory(RMIServerSocketFactory registryServerSocketFactory) {
- this.registryServerSocketFactory = registryServerSocketFactory;
- }
- //設置是否總是創建註冊,而不是試圖查找指定端口上已有的註冊
- public void setAlwaysCreateRegistry(boolean alwaysCreateRegistry) {
- this.alwaysCreateRegistry = alwaysCreateRegistry;
- }
- //設置是否提供已綁定的RMI註冊
- public void setReplaceExistingBinding(boolean replaceExistingBinding) {
- this.replaceExistingBinding = replaceExistingBinding;
- }
- //IoC容器依賴注入完成之後的回調方法
- public void afterPropertiesSet() throws RemoteException {
- prepare();
- }
- //初始化RMI服務導出器
- public void prepare() throws RemoteException {
- //調用其父類RmiBasedExporter的方法,檢查服務引用是否被設置
- checkService();
- //如果服務導出名稱爲null
- if (this.serviceName == null) {
- throw new IllegalArgumentException("Property 'serviceName' is required");
- }
- //檢查socket工廠
- if (this.clientSocketFactory instanceof RMIServerSocketFactory) {
- this.serverSocketFactory = (RMIServerSocketFactory) this.clientSocketFactory;
- }
- if ((this.clientSocketFactory != null && this.serverSocketFactory == null) ||
- (this.clientSocketFactory == null && this.serverSocketFactory != null)) {
- throw new IllegalArgumentException(
- "Both RMIClientSocketFactory and RMIServerSocketFactory or none required");
- }
- //檢查RMI註冊的socket工廠
- if (this.registryClientSocketFactory instanceof RMIServerSocketFactory) {
- this.registryServerSocketFactory = (RMIServerSocketFactory) this.registryClientSocketFactory;
- }
- if (this.registryClientSocketFactory == null && this.registryServerSocketFactory != null) {
- throw new IllegalArgumentException(
- "RMIServerSocketFactory without RMIClientSocketFactory for registry not supported");
- }
- this.createdRegistry = false;
- //獲取RMI註冊器
- if (this.registry == null) {
- this.registry = getRegistry(this.registryHost, this.registryPort,
- this.registryClientSocketFactory, this.registryServerSocketFactory);
- this.createdRegistry = true;
- }
- //獲取要被導出的服務端遠程對象
- this.exportedObject = getObjectToExport();
- if (logger.isInfoEnabled()) {
- logger.info("Binding service '" + this.serviceName + "' to RMI registry: " + this.registry);
- }
- //導出遠程服務對象
- if (this.clientSocketFactory != null) {
- UnicastRemoteObject.exportObject(
- this.exportedObject, this.servicePort, this.clientSocketFactory, this.serverSocketFactory);
- }
- else {
- UnicastRemoteObject.exportObject(this.exportedObject, this.servicePort);
- }
- //將RMI對象綁定到註冊器
- try {
- if (this.replaceExistingBinding) {
- this.registry.rebind(this.serviceName, this.exportedObject);
- }
- else {
- this.registry.bind(this.serviceName, this.exportedObject);
- }
- }
- //異常處理
- catch (AlreadyBoundException ex) {
- unexportObjectSilently();
- throw new IllegalStateException(
- "Already an RMI object bound for name '" + this.serviceName + "': " + ex.toString());
- }
- catch (RemoteException ex) {
- unexportObjectSilently();
- throw ex;
- }
- }
- ……
- }
7.RemoteInvocationBasedExporter處理RMI遠程調用請求:
RmiServiceExporter的父類RmiBasedExporter的父類RemoteInvocationBasedExporter負責對RMI遠程調用請求進行處理,並將處理的結果封裝返回,其源碼如下:
- public abstract class RemoteInvocationBasedExporter extends RemoteExporter {
- //RMI遠程調用處理器,RMI遠程調用請求是由DefaultRemoteInvocationExecutor處理
- private RemoteInvocationExecutor remoteInvocationExecutor = new DefaultRemoteInvocationExecutor();
- //設置RMI遠程調用處理器
- public void setRemoteInvocationExecutor(RemoteInvocationExecutor remoteInvocationExecutor) {
- this.remoteInvocationExecutor = remoteInvocationExecutor;
- }
- //獲取RMI遠程調用處理器
- public RemoteInvocationExecutor getRemoteInvocationExecutor() {
- return this.remoteInvocationExecutor;
- }
- //對RMI遠程調用請求進行處理的地方
- protected Object invoke(RemoteInvocation invocation, Object targetObject)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- if (logger.isTraceEnabled()) {
- logger.trace("Executing " + invocation);
- }
- try {
- //調用DefaultRemoteInvocationExecutor對RMI遠程調用請求進行處理
- return getRemoteInvocationExecutor().invoke(invocation, targetObject);
- }
- catch (NoSuchMethodException ex) {
- if (logger.isDebugEnabled()) {
- logger.warn("Could not find target method for " + invocation, ex);
- }
- throw ex;
- }
- catch (IllegalAccessException ex) {
- if (logger.isDebugEnabled()) {
- logger.warn("Could not access target method for " + invocation, ex);
- }
- throw ex;
- }
- catch (InvocationTargetException ex) {
- if (logger.isDebugEnabled()) {
- logger.debug("Target method failed for " + invocation, ex.getTargetException());
- }
- throw ex;
- }
- }
- //獲取RMI遠程調用請求的處理結果
- protected RemoteInvocationResult invokeAndCreateResult(RemoteInvocation invocation, Object targetObject) {
- try {
- Object value = invoke(invocation, targetObject);
- return new RemoteInvocationResult(value);
- }
- catch (Throwable ex) {
- return new RemoteInvocationResult(ex);
- }
- }
- }
RMI遠程調用請求最終由DefaultRemoteInvocationExecutor處理。
8.DefaultRemoteInvocationExecutor處理RMI遠程調用請求:
DefaultRemoteInvocationExecutor用於處理RMI遠程調用請求,並返回處理後的結果,其源碼如下:
- public class DefaultRemoteInvocationExecutor implements RemoteInvocationExecutor {
- //處理RMI遠程調用請求
- public Object invoke(RemoteInvocation invocation, Object targetObject)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{
- Assert.notNull(invocation, "RemoteInvocation must not be null");
- Assert.notNull(targetObject, "Target object must not be null");
- //調用RemoteInvocation處理RMI遠程調用請求
- return invocation.invoke(targetObject);
- }
- }
9.RemoteInvocation處理RMI遠程調用請求:
RemoteInvocation的invoke方法處理RMI遠程調用請求,並返回遠程調用處理後的結果,其源碼如下:
- public Object invoke(Object targetObject)
- throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
- //使用JDK反射機制獲取遠程調用服務端目標對象的方法
- Method method = targetObject.getClass().getMethod(this.methodName, this.parameterTypes);
- //使用JDK反射機制調用目標對象的方法
- return method.invoke(targetObject, this.arguments);
- }