在发布RMI服务的流程中,有几个步骤可能是我们比较关心的。

获取registry

由于底层的封装,获取Registry实例是非常简单的,只需要使用一个函数LocateRegistry.createRegistry(...)创建Registry实例就可以了。但是,Spring中并没有这么做,而是考虑得更多,比如RMI注册主机与发布的服务并不在一台机器上,那么需要使用LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory)去远程获取Registry实例。

    protected Registry getRegistry(String registryHost, int registryPort,RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)throws RemoteException {if (registryHost != null) {// Host explicitly specified: only lookup possible.if (logger.isInfoEnabled()) {logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");}//如果registryHost不为空则尝试获取对应主机的RegistryRegistry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);//远程连接测试
            testRegistry(reg);return reg;}else {//获取本机的Registryreturn getRegistry(registryPort, clientSocketFactory, serverSocketFactory);}}

如果并不是从另外的服务器上获取Registry连接,那么就需要在本地创建RMI的Registry实例了。当然,这里有一个关键的参数alwaysCreateRegistry,如果此参数配置为true,那么在获取Registry实例时会首先测试是否已经建立了对指定端口的连接,如果已经建立则复用已经创建的实例,否则重新创建。
当然,之前也提到过,创建Registry实例时可以使用自定义的连接工厂,而之前的判断也保证了clientSocketFactory与serverSocketFactory要么同时出现,要么同时不出现,所以这里只对clientSocketFactory是否为空进行了判断。

    protected Registry getRegistry(int registryPort, RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)throws RemoteException {if (clientSocketFactory != null) {if (this.alwaysCreateRegistry) {logger.info("Creating new RMI registry");//使用clientSocketFactory创建Registryreturn LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);}if (logger.isInfoEnabled()) {logger.info("Looking for RMI registry at port '" + registryPort + "', using custom socket factory");}synchronized (LocateRegistry.class) {try {// Retrieve existing registry.//复用测试Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory);testRegistry(reg);return reg;}catch (RemoteException ex) {logger.debug("RMI registry access threw exception", ex);logger.info("Could not detect RMI registry - creating new one");// Assume no registry found -> create new one.return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);}}}else {return getRegistry(registryPort);}}//如果创建Registry实例时不需要使用自定义的套接字工厂,那么就可以直接使用LocateRegistry.createRegistry(...)方法来创建了,当然复用的检测还是必要的。protected Registry getRegistry(int registryPort) throws RemoteException {if (this.alwaysCreateRegistry) {logger.info("Creating new RMI registry");return LocateRegistry.createRegistry(registryPort);}if (logger.isInfoEnabled()) {logger.info("Looking for RMI registry at port '" + registryPort + "'");}synchronized (LocateRegistry.class) {try {// Retrieve existing registry.//查看对应当前registryPort的Registry是否已经创建,如果创建直接使用Registry reg = LocateRegistry.getRegistry(registryPort);//测试是否可用,如果不可用则抛出异常
                testRegistry(reg);return reg;}catch (RemoteException ex) {logger.debug("RMI registry access threw exception", ex);logger.info("Could not detect RMI registry - creating new one");// Assume no registry found -> create new one.//根据端口创建Registryreturn LocateRegistry.createRegistry(registryPort);}}}

初始化将要导出的实体对象

当请求某个RMI服务的时候,RMI会根据注册的服务名称,将请求引导至远程对象处理类中,这个处理类便是使用getObjectToExport()进行创建。

    protected Remote getObjectToExport() {// determine remote object//如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性if (getService() instanceof Remote &&(getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) {// conventional RMI servicereturn (Remote) getService();}else {// RMI invokerif (logger.isDebugEnabled()) {logger.debug("RMI service [" + getService() + "] is an RMI invoker");}//对service进行封装return new RmiInvocationWrapper(getProxyForService(), this);}}

请求处理类的初始化主要处理规则为:如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性,那么直接使用service作为处理类;否则,使用RMIInvocationWrapper对service的代理类和当前类也就是RMIServiceExporter进行封装。
经过这样的封装,客户端与服务端便可以达成一致协议,当客户端检测到是RMIInvocationWrapper类型stub的时候便会直接调用其invoke方法,使得调用端与服务端很好地连接在了一起。而RMIInvocationWrapper封装了用于处理请求的代理类,在invoke中便会使用代理类进行进一步处理。当请求RMI服务时会由注册表Registry实例将请求转向之前注册的处理类去处理,也就是之前封装的RMIInvocationWrapper,然后由RMIInvocationWrapper中的invoke方法进行处理,那么为什么不是在invoke方法中直接使用service,而是通过代理再次将service封装呢?这其中的一个关键点是,在创建代理时添加了一个增强拦截器RemoteInvocationTraceInterceptor,目的是为了对方法调用进行打印跟踪,但是如果直接在invoke方法中硬编码这些日志,会使代码看起来很不优雅,而且耦合度很高,使用代理的方式就会解决这样的问题,而且会有很高的可扩展性。

protected Object getProxyForService(){  //验证service
       checkService();  //验证serviceInterface
       checkServiceInterface();  //使用JDK的方式创建代理  ProxyFactory proxyFactory = new ProxyFactory();  //添加代理接口
        proxyFactory.addInterface(getServiceInterface());  if(registerTraceInterceptor == null ? interceptors == null : registerTraceInterceptor.booleanValue())  //加入代理的横切面RemoteInvocationTraceInterceptor并记录Exporter名称  proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));  if(interceptors != null)  {  AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();  for(int i = 0; i < interceptors.length; i++)  proxyFactory.addAdvisor(adapterRegistry.wrap(interceptors[i]));  }  //设置要代理的目标类
       proxyFactory.setTarget(getService());  proxyFactory.setOpaque(true);  //创建代理  return proxyFactory.getProxy(getBeanClassLoader());  }  

RMI服务激活调用

由于在之前bean初始化的时候做了服务名称绑定this.registry.bind(this.serviceName,thhis.exportedObjedt),其中的exportedObject其实是被RMIInvocationWrapper进行封装过的,也就是说当其他服务调用serviceName的RMI服务时,Java会为我们封装其内部操作,而直接会将代码转向RMIInvocationWrapper测invoke方法中。

public Object invoke(RemoteInvocation invocation) throws RemoteException,         NoSuchMethodException, IllegalAccessException, InvocationTargetException{
  return rmiExporter.invoke(invocation, wrappedObject);
}  

而此时this.rmiExporter为之前初始化的RMIServiceExporter,invocation为包含着需要激活的方法参数,而wrappedObject则是之前封装的代理类。

protected Object invoke(RemoteInvocation invocation, Object targetObject)  throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {  return super.invoke(invocation, targetObject);
}
protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException,       IllegalAccessException, InvocationTargetException {  if(logger.isTraceEnabled())  logger.trace((new StringBuilder()).append("Executing ").append(invocation).toString());  try {  return getRemoteInvocationExecutor().invoke(invocation, targetObject);  }catch(NoSuchMethodException ex){  if(logger.isDebugEnabled())  logger.warn((new StringBuilder())                    .append("Could not find target method for ")                    .append(invocation).toString(), ex);  throw ex;  }  catch(IllegalAccessException ex){  if(logger.isDebugEnabled())  logger.warn((new StringBuilder())                    .append("Could not access target method for ")                    .append(invocation).toString(), ex);  throw ex;  }catch(InvocationTargetException ex){  if(logger.isDebugEnabled())  logger.debug((new StringBuilder()).append("Target method failed for ")                  .append(invocation).toString(), ex.getTargetException());  throw ex;  }  }
public Object invoke(RemoteInvocation invocation, Object targetObject)  throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{  Assert.notNull(invocation, "RemoteInvocation must not be null");  Assert.notNull(targetObject, "Target object must not be null");  //通过反射方式激活方法  return invocation.invoke(targetObject);  }
public Object invoke(Object targetObject)  throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {  //根据方法名称获取代理中的方法  Method method = targetObject.getClass().getMethod(methodName, parameterTypes);  //执行代理中方法  return method.invoke(targetObject, arguments);  }  

targetObject为之前封装的代理类。

SpringRMI解析3-RmiServiceExporter逻辑细节相关推荐

  1. 结果集 tostring_关于避免对toString()结果进行解析或基于逻辑的美德

    结果集 tostring 使用Java或我使用过的其他编程语言,我发现有时候可以用该语言完成某些事情,但通常不应该这样做. 通常,这些误用语言似乎无害,当开发人员首次使用它们时可能是有益的,但后来同一 ...

  2. 关于避免对toString()结果进行解析或基于逻辑的美德

    使用Java或我使用过的其他编程语言,我发现有时可以用该语言完成某些事情,但通常不应该这样做. 通常,这些误用语言似乎无害,当开发人员首次使用它们时可能有益,但后来同一位开发人员或另一位开发人员遇到了 ...

  3. Vue.js 官方团队成员霍春阳新作,深入解析 Vue.js 设计细节

    霍春阳(Hcy),Vue.js 官方团队成员.专注于 Web 研发领域,是 Vue.js 3 的核心贡献者之一,Vue.js 文档生成工具 Vuese 的作者,技术社区活跃者,曾撰写大量颇受好评的技术 ...

  4. Apache Hook机制解析(中)——细节讨论

    本文在上文<Apache Hook机制解析(上)--钩子机制的实现>的基础上,对钩子机制的细节了补充分析. 1.    静态变量_hooks 这个变量由宏APR_HOOK_STRUCT所定 ...

  5. 全面解析并实现逻辑回归(Python)

    本文以模型.学习目标.优化算法的角度解析逻辑回归(LR)模型,并以Python从头实现LR训练及预测. 一.逻辑回归模型结构 逻辑回归是一种广义线性的分类模型且其模型结构可以视为单层的神经网络,由一层 ...

  6. Vue.js 官方团队成员霍春阳新作,深入解析 Vue.js 设计细节【文末送书】

    霍春阳(Hcy),Vue.js 官方团队成员.专注于 Web 研发领域,是 Vue.js 3 的核心贡献者之一,Vue.js 文档生成工具 Vuese 的作者,技术社区活跃者,曾撰写大量颇受好评的技术 ...

  7. mysql逻辑读高影响_运维日记| MySQL/Oracle深度解析之一:逻辑读

    前言 THE FIRST 比较数据库优劣.异同的文章有很多了,使用压测工具,进行不同压力下的测试,就能大致上比较出来哪种数据库是"最快"的数据库.但从有经验的数据架构.DBA等专业 ...

  8. mysql 慢日志 逻辑读_运维日记| MySQLOracle深度解析之一:逻辑读

    前言 THE FIRST 比较数据库优劣.异同的文章有很多了,使用压测工具,进行不同压力下的测试,就能大致上比较出来哪种数据库是"最快"的数据库.但从有经验的数据架构.DBA等专业 ...

  9. oracle运维与mysql_运维日记| MySQL/Oracle深度解析之一:逻辑读

    前言 THE FIRST 比较数据库优劣.异同的文章有很多了,使用压测工具,进行不同压力下的测试,就能大致上比较出来哪种数据库是"最快"的数据库.但从有经验的数据架构.DBA等专业 ...

最新文章

  1. neo4j客户端下载
  2. Python第三周 学习笔记(2)
  3. opencv 图片读取和视频读取(一)
  4. sublime 设置自动更新_不止是自动更新!简单几步让Windows 10变听话的孩子
  5. My favorite books
  6. android自动让输入框上划,Android界面技巧:当输入法调出时,如何让界面自动上移,使输入法不会遮挡到主界面(Activity)...
  7. 《ASP.NET Core 真机拆解》 送书活动结果公布
  8. 林语堂:读书须有胆识,有眼光,有毅力
  9. 在线问卷工具LimeSurvey
  10. python3.8安装pyltp
  11. 浅析 HLS 流媒体协议
  12. java 时区转换 转换成东八区 时间
  13. 大家都可以学的制作个人网
  14. 迈阿密热火队全明星球员克里斯。波什的编程生活
  15. FP Tree算法原理
  16. 图像去雾/图像去雨(matlab/python)
  17. 修改 nz-form-item 的样式
  18. MATLAB||清除指令clear,clear all,clc,clf,cla
  19. solaris IPMP
  20. Unity_MegaFiers_Hump

热门文章

  1. silverlight计时器
  2. WAV格式中常见的压缩编码
  3. ‘MIX_INIT_MP3’ was not declared in this scope,这是什么情况?
  4. VC下几种转换为UNICODE字符串的方法
  5. STM32工作笔记0075---UCOSIII任务管理(下)
  6. Druid实用笔记001---Druid 介绍及配置
  7. Maven异常总结001---Maven project导入到myeclipse时候出现异常:could not get mojo execution paramater value
  8. html5学习笔记---01.HTML5介绍,02.HTML5的新特性
  9. 杭电1492 The number of divisors(约数) about Humble Numbers
  10. cocos2d之z轴位置示例