• 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));
        }
     

     

  • 相关阅读:
    (转)描述线程与进程的区别?
    Python学习笔记:ceil、floor、round、int取整
    Python学习笔记:SQL中group_concat分组合并操作实现
    Python学习笔记:pd.rank排序
    函数声明
    Dictionary 介绍
    Tuple介绍
    List介绍
    DataGridView 如何退出 编辑状态
    C#实现打印与打印预览功能
  • 原文地址:https://www.cnblogs.com/wade-luffy/p/6088048.html
Copyright © 2020-2023  润新知