Spring RMI實現遠端呼叫
1. Spring 除了使用基於 HTTP 協議的遠端呼叫方案,還為開發者提供了基於 RMI 機制的遠端呼叫方法, RMI 遠端呼叫網路通訊實現是基於 TCP/IP 協議完成的,而不是通過 HTTP 協議。
在 Spring RMI 實現中,集成了標準的 RMI-JRIM 解決方案,該方案是 java 虛擬機器實現的一部分,它使用 java 序列化來完成物件的傳輸,是一個 java 到 java 環境的分散式處理技術,不涉及異構平臺的處理。
2.RMI 客戶端配置:
和基於 HTTP 協議的遠端呼叫類似, RMI 遠端呼叫客戶端也需要進行類似如下的配置:
[java] view plain- <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 。
RmiClientInterceptor 對客戶端的遠端呼叫進行攔截,具體的生成遠端呼叫代理物件、查詢遠端呼叫 stub 、以及通過 RMI stub 向服務端發起遠端呼叫請求的具體實現都由 RMI 客戶端攔截器實現,其原始碼如下:
[java] view plaincopyprint?- 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 服務端需要進行類似如下的配置:
[xhtml] view plaincopyprint?- <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 監聽埠和服務匯出的名稱即可。
RmiServiceExporter 主要功能是將服務端遠端物件提供的服務匯出供客戶端請求呼叫,同時將匯出的遠端物件和註冊器繫結起來供客戶端查詢,其主要原始碼如下:
[java] view plaincopyprint?- 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 遠端呼叫請求進行處理,並將處理的結果封裝返回,其原始碼如下:
[java] view plaincopyprint?- 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 RemoteInv