返回介绍

12.1.3 客户端实现

发布于 2025-04-22 22:09:18 字数 9409 浏览 0 评论 0 收藏

根据客户端配置文件,锁定入口类为 RMIProxyFactoryBean,同样根据类的层次结构查找入口函数,如图 12-2 所示。

figure_0360_0049

图 12-2 RMIProxyFactoryBean 类的层次结构图

根据层次关系以及之前的分析,我们提取出该类实现的比较重要的接口 InitializingBean、BeanClassLoaderAware 以及 MethodInterceptor。

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

public void afterPropertiesSet() {

 super.afterPropertiesSet();

  if (getServiceInterface() == null) {

   throw new IllegalArgumentException("Property 'serviceInterface' is

  required");

 }

 //根据设置的接口创建代理,并使用当前类 this 作为增强器

  this.serviceProxy = new ProxyFactory(getServiceInterface(), 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 {

  // Cache RMI stub on initialization?

 //如果配置了 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();

   //验证传输协议

    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.registry

    Client SocketFactory);

    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 服务器,用户通过实现 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 (!this.cacheStub || (this.lookupStubOnStartup && !this.refreshStubOnConnect

  Failure)) {

  //如果有缓存直接使用缓存

  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 是 RMIInvocation Handler,就意味着服务端也同样使用了 Spring 去构建,那么自然会使用 Spring 中作的约定,进行客户端调用处理。Spring 中的处理方式被委托给了 doInvoke 方法。

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

protected Object doInvoke(MethodInvocation invocation, Remote stub) throws Throwable {

 //stub 从服务器传回且经过 Spring 的封装

  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("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, Invocation

  TargetException {

  if (AopUtils.isToStringMethod(methodInvocation.getMethod())) {

   return "RMI invoker proxy for service URL [" + getServiceUrl() + "]";

 }

 //将 methodInvocation 中的方法名及参数等信息重新封装到 RemoteInvocation,并通过远程代理

 方法直接调用

  return invocationHandler.invoke(createRemoteInvocation(methodInvocation));

}

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。