SpringRMI解析4-客戶端實現


根據客戶端配置文件,鎖定入口類為RMIProxyFactoryBean,同樣根據類的層次結構查找入口函數。

     <bean id="rmiServiceProxy" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">  
        <property name="serviceUrl">  
            <value>rmi://localhost/rmiService</value>  
        </property>  
        <property name="serviceInterface">  
            <value>org.spring.RmiService</value>  
        </property>  
    </bean> 

根據層次關系,我們提取出該類實現的比較重要的接口InitializingBean,BeanClassLoaderAware以及MethodInterceptor

public class RmiProxyFactoryBean extends RmiClientInterceptor  
    implements FactoryBean, BeanClassLoaderAware 

其中繼承了RMIClientInterceptor這個類,這個類的父類的父類實現了InitializingBean接口,則spirng會確保在此初始化bean時調用afterPropertiesSet進行邏輯的初始化。

public void afterPropertiesSet()  
{  
    super.afterPropertiesSet();  
    if(getServiceInterface() == null)  
    {  
        throw new IllegalArgumentException("Property 'serviceInterface' is required");  
    } else  
    {  
       //根據設置的接口創建代理,並使用當前類this作為增強器  
       serviceProxy = (new ProxyFactory(getServiceInterface(), this)).getProxy(getBeanClassLoader());  
        return;  
    }  
}  

同時,RMIProxyFactoryBean又實現了FactoryBean接口,那么當獲取bean時並不是直接獲取bean,而是獲取該bean的getObject方法。

public Object getObject()  
{  
    return serviceProxy;  
}  

這樣,我們似乎已經形成了一個大致的輪廓,當獲取該bean時,其實返回的是代理類,既然調用的是代理類,那么又會使用當前bean作為增強器進行增強,也就是說會調用RMIProxyFactoryBean的父類RMIClientInterceptor的invoke方法。

afterPropertiesSet()

public void afterPropertiesSet()  
{  
    super.afterPropertiesSet();  
    prepare();  
}  
//繼續追蹤代碼,發現父類的父類,也就是UrlBasedRemoteAccessor中的afterPropertiesSet方法只完成了對serviceUrl屬性的驗證。
public void afterPropertiesSet()  
{  
    if(getServiceUrl() == null)  
        throw new IllegalArgumentException("Property 'serviceUrl' is required");  
    else  
        return;  
}  

在父類的afertPropertiesSet方法中完成了對serviceUrl的驗證,那么prepare函數完成了什么功能呢?

  1. 通過代理攔截並獲取stub

  2. 增強器進行遠程連接

通過代理攔截並獲取stub

public void prepare() throws RemoteLookupFailureException -{  
        //如果配置了lookupStubOnStartup屬性便會在啟動時尋找stub  
       if(lookupStubOnStartup)  
        {  
            Remote remoteObj = lookupStub();  
            if(logger.isDebugEnabled())  
                if(remoteObj instanceof RmiInvocationHandler)  
                    logger.debug((new StringBuilder())
                .append("RMI stub [")
                .append(getServiceUrl())
                .append("] is an RMI invoker").toString()); else if(getServiceInterface() != null) { boolean isImpl = getServiceInterface().isInstance(remoteObj); logger.debug((new StringBuilder())
                .append("Using service interface [")
                .append(getServiceInterface().getName())
                .append("] for RMI stub [")
                .append(getServiceUrl()).append("] - ")
                .append(isImpl ? "" : "not ")
                .append("directly implemented").toString()); } if(cacheStub) //將獲取的stub緩存 cachedStub = remoteObj; } }

從上面的代碼中,我們了解到了一個很重要的屬性lookupStubOnStartup,如果將此屬性設置為true,那么獲取stub的工作就會在系統啟動時被執行緩存,從而提高使用時候的響應時間。獲取stub是RMI應用中的關鍵步驟,當然你可以使用兩種方式進行。

(1)使用自定義的套接字工廠。如果使用這種方式,你需要在構建Registry實例時將自定義套接字工廠傳入並使用Registry中提供的lookup方法來獲取對應的stub。

(2)套接使用RMI提供的標准方法,Naming.lookup(getServiceUrl()).

protected Remote lookupStub()throws RemoteLookupFailureException{  
    try{  
        Remote stub = null;  
        if(registryClientSocketFactory != null)  
        {  
            URL url = new URL(null, getServiceUrl(), new DummyURLStreamHandler());  
            String protocol = url.getProtocol();  
            if(protocol != null && !"rmi".equals(protocol))  
                throw new MalformedURLException((new StringBuilder())
                .append("Invalid URL scheme '")
                .append(protocol)
                .append("'").toString()); String host = url.getHost(); int port = url.getPort(); String name = url.getPath(); if(name != null && name.startsWith("/")) name = name.substring(1); Registry registry = LocateRegistry.getRegistry(host, port, registryClientSocketFactory); stub = registry.lookup(name); } else { stub = Naming.lookup(getServiceUrl()); } if(logger.isDebugEnabled()) logger.debug((new StringBuilder())
            .append("Located RMI stub with URL [")
            .append(getServiceUrl())
            .append("]").toString()); return stub; } catch(MalformedURLException ex) { throw new RemoteLookupFailureException((new StringBuilder())
            .append("Service URL [")
            .append(getServiceUrl())
            .append("] is invalid").toString(), ex); } catch(NotBoundException ex) { throw new RemoteLookupFailureException((new StringBuilder())
            .append("Could not find RMI service [")
            .append(getServiceUrl())
            .append("] in RMI registry").toString(), ex); } catch(RemoteException ex) { throw new RemoteLookupFailureException("Lookup of RMI stub failed", ex); } }

為了使用registryClientSocketFactory,代碼量比使用RMI標准獲取stub方法多出了很多,那么registryClientSocketFactory到底是做什么用的呢?與之前服務端的套接字工廠類似,這里的registryClientSocketFactory用來連接RMI服務器,用戶通過實現RMIClientSocketFactory接口來控制用於連接的socket的各種參數。

增強器進行遠程連接

在初始化時,創建了代理並將本身作為增強器加入了代理中(RMIProxyFactoryBean間接實現了MethodInterceptor),那么這樣一來,當在客戶端調用代理的接口中的某個方法時,就會首先執行RMIProxyFactoryBean中的invoke方法進行增強。

public Object invoke(MethodInvocation invocation) throws Throwable {  
   //獲取服務器中對應的注冊的remote對象,通過序列化傳輸  
   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 Remote getStub()throws RemoteLookupFailureException {  
  //如果有緩存,直接使用緩存  
  if(!cacheStub || lookupStubOnStartup && !refreshStubOnConnectFailure){  
    return cachedStub == null ? lookupStub() : cachedStub;  
  }else
{
    synchronized (this.stubMonitor) {
      if (this.cachedStub == null) {
this.cachedStub = lookupStub();
}
return this.cachedStub;
}
}

當客戶端使用接口進行方法調用時時通過RMI獲取stub的,然后再通過stub中封裝的信息進行服務器的調用,這個stub就是在構建服務器時發布的對象,那么客戶端調用時的最關鍵的一步也是進行stub的獲取了。

當獲取到stub后便可以進行遠程方法的調用了。Spring中對於遠程方法的調用其實是分兩種情況考慮的。

  • 獲取的stub是RMIInvocationHandler類型的,從服務端獲取的stub是RMIInvocationHandler,就意味着服務端也同樣使用了Spring去構建,那么自然會使用Spring中作的約定,進行客戶端調用處理。Spring中的處理方式被委托給了doInvoke方法。
  • 當獲取的stub不是RMIInvocationHandler類型,那么服務端構建RMI服務可能是通過普通的方法或者借助於Spring外的第三方插件,那么處理方式自然會按照RMI中普通的方式進行,而這種普通的處理方式無非是反射。因為在invocation中包含了所需要調用的方法的各種信息,包括方法名稱以及參數等,而調用的實體正是stub,那么通過反射方法完全可以激活stub中的遠程調用。
    protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
        if (stub instanceof RmiInvocationHandler) {
            // RMI invoker
            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);
            }
        }
        else {
            // traditional RMI stub
            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;
                }
            }
        }

在分析服務端發布RMI的方式時,Spring將RMI的到處Object封裝成了RMIInvocationHandler類型進行發布,那么當客戶端獲取stub的時候是包含了遠程連接信息代理類的RMIInvacationHandler,也就是說當調用RMIInvacationHandler中的方法時會使用RMI中提供的代理進行遠程連接,而此時,Spring中要做的就是將代碼引向RMIInvocationHandler接口的invoke方法的調用。

    protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
        throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {

        if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {
            return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";
        }
    //將methodInvocation中的方法名以及參數等信息重新封裝RemoteInvocation,並通過遠程代理方法直接調用
        return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
    }

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM