Spring源碼與分析總結——RMI整合

該文章基於《Spring源碼深度解析》撰寫,感謝郝佳老師的奉獻
RMI的實際作用,是通過暴露對應方法的URL,從而實現高解耦。
RMI服務的流程是通過服務的發佈服務的調用組成,小Demo如下:

其工程架構如下
工程結構圖
其對應文件代碼如下

/*HelloRMIService*/
package RMIService.Impl;

public class HelloRMIService implements RMIService.HelloRMIService{
    @Override
    public int getAdd(int a, int b) {
        return a+b;
    }
}
/*RMIService*/
package RMIService;

public interface HelloRMIService {
    public int getAdd(int a,int b);
}
/*RMIService*/
package RMIService;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RMIServer {

    public static void main(String[] args) throws InterruptedException {
        new ClassPathXmlApplicationContext("server.xml");
    }
}
/*RMIClient*/
package RMIClient;

import RMIService.HelloRMIService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class RMIClient {


    public static void main(String[] args) {
        ApplicationContext ctx = new ClassPathXmlApplicationContext(
                "Client.xml");
        HelloRMIService accountService = (HelloRMIService) ctx
                .getBean("mobileAccountService");
        Integer result = accountService.getAdd(1,2);
        System.out.println(result);
    }

}
<!--Client-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="mobileAccountService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
        <property name="serviceUrl" value="rmi://localhost:8080/MobileAccountService" />
        <property name="serviceInterface"
                  value="RMIService.HelloRMIService" />
    </bean>

</beans>
<!--server.xml-->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
        <property name="serviceName" value="MobileAccountService" />
        <property name="service" ref="accountService" />
        <property name="serviceInterface"
                  value="RMIService.HelloRMIService" />
        <property name="registryPort" value="8080" />
        <property name="servicePort" value="8088" />
    </bean>

    <bean id="accountService" class="RMIService.Impl.HelloRMIService" />

</beans>

需要注意的是,我們需要將HelloRMIService.class文件打包爲jar文件後放入lib中。
現在我們開始進行流程分析:

服務器端實現

org.Springframework.remoting.RMI.RMIServiceExporter是RMI服務發佈的關鍵類,該類定義在server.xml中。Spring中的消息開啓很簡單,通過RMIServer類中的

new ClassPathXmlApplicationContext("server.xml");

開啓服務。RMIServiceExporte的類層次結構如下:
RmiServiceExporter
RmiServiceExporter實現了幾個比較敏感的接口:BeanClassLoaderAware(保證實現該接口的Bean的初始化時調用其setBeanClassLoader方法)、DisposableBean(銷燬Bean時調用其destory方法)、InitializingBean(初始化Bean時調用其afterPropertiesSet方法),所以RmiServiceExporter的初始化應該位於BeanClassLoaderAware或者InitializingBean中,實際上初始化方法位於InitializingBean的afterPropertiesSet方法,該方法的處理邏輯爲:
(1)驗證Service
(2)處理用戶自定義的SocketFactory屬性,Spring中提供了4個套接字工廠配置分別是(client/server)+SocketFactory,register+(client/server)+SocketFactory,register+(client/server)+SocketFactory用於當創建registry實例時在RMI主機通過serverSocketFactory創建套接字並等待連接,而在服務器端與RMI主機通信時通過clientSocketFacotry創建套接字
(3)根據配置參數獲取Registry
如果RMI註冊主機與發佈服務的機器是同一臺機器,那麼可以直接調用函數LocateRegistry.createRegistry(…)創建Registry實例。如果並不在同一臺機器上,那麼使用LocateRegistry.getRegistry(registryHost,registryPort,clientSocketFactory)進行遠程獲取,如果已經建立了連接,那麼將直接複用連接,除非alwaysCreateRegistry爲true如果我們自定義了連接工廠——由於clientSocketFactory和serverSocketFactory只能可同時存在或者同時不存在,所以我們只對clientSocketFactory是否爲空進行判斷如果不存在自定義的連接工廠——直接進行復用檢測,如果不存在或者alwaysCreateRegistry爲true,那麼直接通過LocateRegistry.createRegistry(…)創建新連接。
(4)構造對外發布的實例當外界通過註冊的服務名調用響應方法時,RMI服務會將請求引入此類來處理,如果配置的service屬性對應的類實現了remote接口但是沒有配置serviceInterface接口,那麼直接使用service作爲處理類,否則使用用RMIInvocationWrapper對RMIServiceExporter進行封裝,通過封裝使客戶端與服務器端達成了一致,並且在創建代理時添加了增強攔截器RemoteInvocationTraceIntercaptor,如果直接通過硬編碼,那麼會很不優雅,通過動態代理使代碼具有高擴展性。正是由於封裝的情況,當調用服務時實際上調用的是RMIInvocationWrappe的invoke方法。
(5)發佈實例

客戶端實現

客戶端的入口類爲RMIProxyFactoryBean,其類層次結構如下所示:
RMIProxyFactoryBean
該類實現了幾個比較關鍵的接口,InitailizingBean接口(通過afterPropertiesSet進行邏輯初始化),FactoryBean接口(getBean方法實際上返回的是實現類的getObject方法返回的實例),下面重點介紹InitailizingBean接口接口在初始化時的邏輯:
需要注意的是,afterPropertiesSet實際上的處理邏輯位於RmiClientInterceptor類中的prepare方法中,所以此處對該方法進行邏輯分析
(1)通過代理攔截並獲取stub
獲取stub可以通過兩種方式進行:使用自定義的套接字工廠該方法需要在構造Registry實例時將自定義套接字工廠傳入,並使用lookup來獲取對應stub,直接使用RMI提供的標準方法Naming.lookup(getServiceUrl()),該方法中將會使用registryClientSocketFactory(實現了RMIClientSocketFactory接口)用於控制用於連接的socket的各種參數然後連接RMI服務器
(2)增強器用於遠程連接
由於RmiProxyFactoryBean間接實現了MethodInterceptor,那麼當客戶端調用某個方法時,會首先調用invoke方法進行增強,invoke方法的邏輯如下:
(1)獲取服務器中對應註冊的remote對象,通過序列化傳輸,在通過getstub方法獲取stub時,會首先檢查有沒有stub的緩存
(2)遠程方法調用分爲兩種情況,第一種情況是:獲取的stub是RMIInvocationHandler類型的,這就意味着客戶端和服務端都是通過Spring框架搭建的,處理方式爲將處理方式委託給doInvoke方法第二種情況是:獲取的stub不是RMIInvocationHandler類型的,那麼這也意味着我們將通過反射的方法激活stub中的遠程調用

發佈了52 篇原創文章 · 獲贊 8 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章