zoukankan      html  css  js  c++  java
  • RMI实现-----客户端的实现

    根据远程服务(RMI)使用示例文章中的客户端的配置文件,锁定入口类为RmiProxyFactoryBean,先来看一下它的类图:

    该类实现的比较重要的接口有InitializingBean、BeanClassLoaderAware以及MethodInterceptor。

    其中实现了InitializingBean,则Spring会确保在此初始化bean时调用afterPropertiesSet进行逻辑的初始化。

    public void afterPropertiesSet() {
            super.afterPropertiesSet();
            Class<?> ifc = getServiceInterface();
            Assert.notNull(ifc, "Property 'serviceInterface' is required");
            //根据设置的接口创建代理,并使用当前类的this作为增强器
            this.serviceProxy = new ProxyFactory(ifc, this).getProxy(getBeanClassLoader());
        }

    同时,RmiProxyFactoryBean又实现了FactoryBean接口,那么当获取bean时并不是直接获取bean,而是获取该bean的getObject方法。

    public Object getObject() {
            return this.serviceProxy;
        }

    这样,我们大致已经形成了一个轮廓,当获取该bean时,首先通过afterPropertiesSet方法创建代理类,并使用当前类作为增强方法,而在调用该bean时其实返回的时代理类,既然调用的是代理类,那么又会使用当前bean作为增强器进行增强,也就是说会调用RmiProxyFactoryBean的父类RmiClientInterceptor的invoke方法。

    我们先从afterPropertiesSet中的super.afterPropertiesSet()方法开始分析。

    public void afterPropertiesSet() {
            super.afterPropertiesSet();
            prepare();
        }

    继续追踪代码,发现父类的父类,也就是UrlBasedRemoteAccessor中的afterPropertiesSet方法只是完成了对serviceUrl属性的验证。

    public void afterPropertiesSet() {
            if (getServiceUrl() == null) {
                throw new IllegalArgumentException("Property 'serviceUrl' is required");
            }
        }

    所以推断,所有的客户端都应该在prepare方法中实现,继续查看prepare方法。 

    1.通过代理拦截并获取stub

     在父类的afterPropertiesSet方法中完成了对serviceUrl的验证,那么prepare函数又完成了什么功能呢?

    public void prepare() throws RemoteLookupFailureException {
            //如果配置了lookupStubOnStartup属性便会在启动时寻找stub
            if (this.lookupStubOnStartup) {
                Remote remoteObj = lookupStub();
                if (logger.isDebugEnabled()) {
                    if (remoteObj instanceof RmiInvocationHandler) {
                        logger.debug("RMI stub [" + getServiceUrl() + "] is an RMI invoker");
                    }
                    else if (getServiceInterface() != null) {
                        boolean isImpl = getServiceInterface().isInstance(remoteObj);
                        logger.debug("Using service interface [" + getServiceInterface().getName() +
                            "] for RMI stub [" + getServiceUrl() + "] - " +
                            (!isImpl ? "not " : "") + "directly implemented");
                    }
                }
                if (this.cacheStub) {
                    //将获取的stub进行缓存
                    this.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 (this.registryClientSocketFactory != null) {
                    URL url = new URL(null, getServiceUrl(), new DummyURLStreamHandler());
                    String protocol = url.getProtocol();
                    //z验证传输协议
                    if (protocol != null && !"rmi".equals(protocol)) {
                        throw new MalformedURLException("Invalid URL scheme '" + protocol + "'");
                    }
                    //主机
                    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, this.registryClientSocketFactory);
                    stub = registry.lookup(name);
                }
                else {
                    // Can proceed with standard RMI lookup API...
                    stub = Naming.lookup(getServiceUrl());
                }
                if (logger.isDebugEnabled()) {
                    logger.debug("Located RMI stub with URL [" + getServiceUrl() + "]");
                }
                return 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);
            }
        }

    为了使用registryClientSocketFactory,代码量比使用RMI标准获取stub方法多出了很多,那么 registryClientSocketFactory到底是做什么用的呢?

      与之前服务端的套接字工厂类似,这里的registryClientSocketFactory用来连接RMI服务器,用户通过实现registryClientSocketFactory接口来控制用于连接的socket的各种参数。

    2.增强器进行远程连接

     之前分析了类型为RmiProxyFactoryBean的bean的初始化中完成的逻辑操作。在初始化时,创建了代理并将本身作为增强器加入了代理中(RmiProxyFactoryBean间接实现了MethodInterceptor),那么这样一来,当客户端调用代理的接口中的某个方法时,就会首先执行RmiClientInterceptor中的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;
                }
            }
        }

     众所周知,当客户端使用接口进行方法调用时是通过RMI获取stub的,然后再通过stub中封装的信息进行服务器的调用,这个stub就是在构建服务器发布的对象,那么,客户端调用时最关键的一步也是进行stub的获取了。

    protected Remote getStub() throws RemoteLookupFailureException {
            if (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnectFailure)) {
                //有缓存直接使用缓存
                return (this.cachedStub != null ? this.cachedStub : lookupStub());
            }
            else {
                synchronized (this.stubMonitor) {
                    if (this.cachedStub == null) {
                        //获取stub
                        this.cachedStub = lookupStub();
                    }
                    return this.cachedStub;
                }
            }
        }

    当获取到stub后便可以进行远程方法的调用了。Spring中对于远程方法的调用其实是分两种情况考虑的:

      ❤ 获取的stub是RMIInvocationHandler类型的,从服务端获取的stub是RMIInvocationHandler,就意味着服务端也同样使用了Spring去构建,那么自然会使用Spring中作的约定,进行客户端调用处理。Spring中的处理方式被委托给了doInvoke方法。

      ❤ 当获取的stub不是RMIInvocationHandler类型,那么服务端构造RMI服务可能是通过普通的方法或者借助于Spring外的第三方插件,那么处理方式自然会按照RMI中普通方式进行,而这种普通的方式无非是反射。因为在invoke中包含了所需要调用方法的各种信息,包括方法名称以及参数等,而调用的实体正是stub,那么通过反射方法完全可以激活stub中的远程调用。

    protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {
            //stub从服务器传回且经过Spring的封装
            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 {
                // 直接使用反射方法继续激活
                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;
                    }
                }
            }
        }

    之前反复的提到了Spring中的客户端处理RMI的方式。其实,在分析服务端发布RMI的方式时,我们已经了解到,Spring将RMI导出Object封装成了RMIInvocationHandler类型进行发布,那么当客户端获取stub的时候包含了远程连接信息代理类RMIInvocationHandler,也就是说当调用RMIInvocationHandler中的方法时会使用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));
        }

     至此,RMI中的客户端的解析已经完毕。

    参考:《Spring源码深度解析》 郝佳 编著: 

  • 相关阅读:
    使用postMan调用web services wsdl接口
    Python的入门基础(Linux、python、git)
    CrossoverQA文档
    Linux_磁盘分区、挂载、查看
    Linux为什么要挂载
    图解Windows10+优麒麟双系统安装
    Linux 软件安装与卸载
    ventroy 制作多系统启动盘
    字节跳动面试官:请你实现一个大文件上传和断点续传
    关于本博客和博主
  • 原文地址:https://www.cnblogs.com/Joe-Go/p/10309179.html
Copyright © 2011-2022 走看看