远程服务RMI源码解析(三)客户端实现

RMI客户端源码解析:

根据客户端配置文件,锁定入口类为RMIProxyFactoryBean,

客户端配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd">
	<bean id="myClient" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
		<property name="serviceUrl" value="RMI://127.0.0.1:1099/helloRMI"/>
		<property name="serviceInterface" value="org.tarena.note.service.HelloRMIService"/>
	</bean>
</beans>

根据层次关系,我们提取出该类实现的比较重要的接口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时i,其实返回的是代理类,既然调用的是代理类,那么又会使用当前bean作为增强器进行增强,也就是说会调用RMIProxyFactoryBean的父类RMIClientInterceptor的invoke方法。


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

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

继续追踪代码,发现父类的父类,也就是UrlBasedRemoteAccessor中的afterPropertiesSet方法只完成了对serviceUrl属性的验证。
    public void afterPropertiesSet()
    {
        if(getServiceUrl() == null)
            throw new IllegalArgumentException("Property 'serviceUrl' is required");
        else
            return;
    }

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


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

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

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的各种参数。

2.增强器进行远程连接

之前分析了类型为RMIProxyFactoryBean的bean的初始化中完成的逻辑操作。在初始化时,创建了代理并将本身作为增强器加入了代理中(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;
        }
    }

中所周知,当客户端使用接口进行方法调用时时通过RMI获取stub的,然后再通过stub中封装的信息进行服务器的调用,这个stub就是在构建服务器时发布的对象,那么客户端调用时的最关键的一步也是进行stub的获取了。
protected Remote getStub()
        throws RemoteLookupFailureException
    {
        //如果有缓存,直接使用缓存
       if(!cacheStub || lookupStubOnStartup && !refreshStubOnConnectFailure)
            return cachedStub == null ? lookupStub() : cachedStub;
当获取到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)
                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((new StringBuilder()).append("Invocation of method [").append(invocation.getMethod()).append("] failed in RMI service [").append(getServiceUrl()).append("]").toString(), ex);
                }
            Throwable targetEx;
            try
            {
                return RmiClientInterceptorUtils.invokeRemoteMethod(invocation, stub);
            }
            catch(InvocationTargetException ex)
            {
                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的时候是包含了远程连接信息代理类的RMIInvacationHandler,也就是说当调用RMIInvacationHandler中的方法时会使用RMI中提供的代理进行远程连接,而此时,Spring中要做的就是讲代码引向RMIInvocationHandler接口的invoke方法的调用。
        protected Object doInvoke(MethodInvocation methodInvocation, RmiInvocationHandler invocationHandler)
            throws RemoteException, NoSuchMethodException, IllegalAccessException, InvocationTargetException
        {
            if(AopUtils.isToStringMethod(methodInvocation.getMethod()))
                return (new StringBuilder()).append("RMI invoker proxy for service URL [").append(getServiceUrl()).append("]").toString();
            else
                                     //将methodInvocation中的方法名以及参数等信息重新防撞RemoteInvocation,兵通过远程代理方法直接调用
  •            return invocationHandler.invoke(createRemoteInvocation(methodInvocation));
        }



  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值