RMI
使用示例
建立 RMI 对外接口
建立接 口实现类
建立服务端配置文件。
建立服务端测试
完成了服务端的配置后,还需要在测试端建立测试环境以及测试代码 首先建立测试
端配置文件
编写测试代码
服务端实现
启动 Spring 中的 RMI 服务并没有多余的操作,仅仅是开启 Spring
的环境 new ClassPathXmlApplicationContext(”test/remote/RMIServer.xml ”),仅此一句 于是,
分析很可能是 RMIServiceExportern 在初始化的时候做了某些操作完成了端口的发布功能,
那么这些操作的人口是在这个类的哪个方法里面呢?
进入这个类,首先分析这个类的层次结构
RMIServiceExporter 实现了 Spring 中几个比较敏感的接口: BeanClassLoaderAware
DisposableBean InitializingBean 其中, DisposableBean 接口保证在实现该接 口的 bean 销毁时
调用其 destroy 方法, BeanClassLoaderAware 接口保证在实现该接口的 bean 的初始化时调用其
setBeanClassLoader 方法,而 InitializingBean 接口则是保证在实现该接 口的 bean 初始化时调用
其 afterPropertiesSet 方法,所以推断 RMIServiceExporter 的初始化函数人口一定在其
afterPropertiesSet 或者 setBeanClassLoader 方法中 经过查看代码, 确认 afterPropertiesSet 为
RMIServiceExporter 功能的初始化人口
在 afterPropertiesSet 函数中将实现委托给了 prepare ,而 prepare 方法中找到
了 RMI 服务发布的功能实现,大致清楚了 RMI 服务发布的流程
验证 service
处理用户自定义的 SocketFactory 属性
根据配置参数获取 Registry
构造对外发布的实例
发布实例
获取 registry
由于底层的封装,获取 Registry 实例是非常简单的,只需要使用
一个函数 LocateRegistry.createRegistry( … )创建 Registry 实例就可以了 但是, Spring 中并没有这么
做,而是考虑得更多,比如 RMI 注册主机与发布的服务并不在一台机器上,那么需要使用
LocateRegistry.getRegistry(registryHost registryPost, clientSocketFactory)去远程获取 Registry 实例
创建 Registry 实例时可以使用自定义的连接工厂, 而之前的判断也
保证了 clientSocketFactory 与 serverSocketFactory 要么 同时出现 ,要么 同时不出现,所以这里只
对 clientSocketFactory 是否为空进行了判断
如果创建 Registry 实例时不需要使用自定义的套接字工厂 ,那么就可以直接使用
LocateRegistry.createRegistry( … )方法来创建了,当然复用的检测还是必要的
初始化将要导出的实体对象
当请求某个 RMI 服务的时候, RMI 会根据注册的服务名称,将请求引导
至远程对象处理类中,这个处理类便是使用 getObjectToExport()进行创建
当请求 RMI 服务时会由注册表 Registry 实例将请求转向之前
注册的处理类去处理, 也就是之前封装的 RMIInvocationWrapper 然后由 RMIInvocationWrapper
中的 invoke 方法进行处理,那么为什么不是在 invoke 方法中直接使用 service 而是通过代理
再次将 service 封装呢?
这其中的一个关键点是,在创建代理时添加了一个增强拦截器 RemoteinvocationTraceinterceptor
目的是为了对方法调用进行打印跟踪 ,但是如果直接在 invoke 方法中硬编码这些日志,会使代
码看起来很不优雅,而且耦合度很高,使用代理的方式就会解决这样的问题,而且会有很高的
可扩展性
RMI 服务激活调用
由于在之前 bean 初始化的时候做了服务名称绑定 this.registry.bind
(this.serviceName, this.exportedObject ,其中的 exportedObject 其实是被 RMIInvocationWrapper
进行过封装的,也就是说当其他服务器调用 serviceName 的 RMI 服务时, Java 会为我们封装其
内部操作,而直接会将代码转向 RMIInvocationWrapper 的 invoke 方法中。
而此时 this.RMIExporter 为之前初始化的 RMIServiceExporter, invocation 为包含着需要激
活的方法参数,而 wrappedObject 则是之前封装的代理类
客户端实现
根据客户端配置文件,锁定入口类为 RMIProxyFactoryBean ,同样根据类的层次结构查找
入口函数
其中实现了 InitializingBean ,则 Spring 会确保在此初始化 bean 时调用 afterPropertiesSet 进行
逻辑的初始化
同时, RMIProxyFactoryBean 又实现了 FactoryBean 接口,那么当获取 bean 时并不是直接
获取 bean ,而是获取该 bean 的 getObject 方法
当获取该 bean 时, 首先通过 afterPropertiesSet
创建代理类,并使用当前类作为增强方法,而在调用该 bean 时其实返回的是代理类,既然调
用的是代理类 ,那么又会使用当前 bean 作为增强器进行增强,也就是说会调用 RMIProxy
FactoryBean 的父类 RMICIientlnterceptor 的 invoke 方法
先从 afterPropertiesSet 中的 super.afterPropertiesSet()方法开始分析
继续追踪代码,发现父类的父类,也就是 UrlbasedRemoteAccessor 中的 afterPropertiesSet
方法只完成了对 serviceUrl 属性的验证
所以推断所有的客户端都应该在 prepare 方法中实现,继续查看 prepare()
通过代理拦截井获取 stub
lookupStubonStartup ,如果将此属性
设置为 true ,那么获取 stub 的工作就会在系统启动时被执行并缓存,从而提高使用时候的响
应时间
获取 stub 是 RMI 应用中的关键步骤 当然你可以使用两种方式进行
使用自定义的套接字工厂 如果使用这种方式, 需要在构造 Registry 实例时将自
定义套接字工厂传入,并使用 Registry 中提供的 lookup 方法来获取对应的 stub
直接使用 RMI 提供的标准方法 Naming.lookup(getServiceUrl())
增强器进行远程连接
之前分析了类型为 RMIProxyFactoryBean 的 bean 的初始化中完成的逻辑操作 在初始化时,
创建了 代理 并将本身作为增强器 加入了代理中( RMIProxyFactoryBean 简接实现了
Methodlnterceptor 那么这样一来,当在客户端调用代理的接口中的某个方法时,就会首先执行
RMIProxyFactoryBean 中的 invoke 方法进行增强
当客户端使用接口进行方法调用时是通过 RMI 获取 stub 的,然后再通过 stub
中封装的信息进行服务器的调用,这个 stub 就是在构建服务器时发布的对象 ,那么, 客户端调
用时最关键的一步也是进行 stub 的获取了
当获取到 stub 后便可以进行远程方法的调用了 Spring 中对于远程方法的调用其实是分
两种情况考虑的
获取的 stub 是 RMIInvocationHandler 类型的,从服务端获取的 stub 是 RMIInvocation
Handler ,就意味着服务端也同样使用了 Spring 去构建,那么 自然会使用 Spring 中作
的约定,进行客户端调用 处理 Spring 中的处理方式被委托给了 dolnvoke 方法
当获取的 stub 不是 RMIInvocationHandler 类型,那么服务端构建 RMI 服务可能是通过
普通的方法或者借助于 Spring 外的第二方插件,那么处理方式自然会按照 RMI 中普
通的处理方式进行,而这种普通的处理方式无非是反射 因为在 invocation 中包含了
所需要调用的方法的各种信息,包括方法名称以及参数等, 而调用的实体正是 stub,
那么通过反射方法完全可以激活 stub 中的远程调用
之前反复提到了 Spring 中的客户端处理 RMI 的方式。其实,在分析服务端发布 RMI 的方
式时,已经了解到, Spring 将 RMI 的导出 Object 封装成了 RMIInvocationHandler 类型进
行发布,那么当客户端获取 stub 的时候是包含了远程连接信息代理类的 RMIInvocationHandler,
也就是说当调用 RMIInvocationHandler 中的方法时会使用 RMI 中提供的代理进行远程连接,而
此时, Spring 中要做的就是将代码引向 RMIlnvocationHandler 接口的 invoke 方法的调用
Httplnvoker
使用示例
创建对外接口
创建接口实现类
创建服务端配置文件 applicationContext-server.xml
在 WEB-INF 下创建 remote-servlet.xml
创建测试端配置 client.xml
创建测试类
运行测试类,会看到打印结果:
getTestPo dddd
服务端实现
根据 remote-servlet.xml 中的配置,分析人口类应该为 org.Springframework.remoting.
httpinvoker.HttpInvokerServiceExporter ,那么同样,根据这个类分析其人口函数
通过层次关系我们看到 HttplnvokerService Exporter 类实现了 InitializingBean 接口以及 Http
RequestHandler 接口 分析 RMI 服务时我们已经了解到了,当某个 bean 继承自 lnitializingBean
接口的时候,Spring 会确保这个 bean 在初始化时调用其 afterPropertiesSet 方法,而对于
HttpRequestHandler 接口,因为在配置中已经将此接口配置成 Web 服务,那么当有相应请
求的时候, Spring 的 Web 服务就会将程序引导至 HttpRequestHandler 的 handleRequest 方法中
首先,我们从 afterPropertiesSet 方法开始分析,看看在 bean 的初始化过程中做了哪些逻辑
创建代理
处理来自客户端的 request
当有 Web 请求时,根据配置中的规则会把路径匹配的访问直接引入对应的 HttpRequest
Handler 中 本例中的 Web 请求与普通的 Web 请求是有些区别的 因为此处的请求包含着
Httplnvoker 的处理过程
HttpInvoker服务简单点说就是将请求的方法 也就是 Remotelnvocation 对象 从客户端序列化并通过 Web
请求出入服务端,服务端在对传过来的序列化对象进行反序列化还原 Remotelnvocation 实例,
然后通实例中的相关信息进行相关方法的调用,并将执行结果再次的返回给客户端 从
handleRequest 函数中也可以清晰地看到程序执行的框架结构
从request 中读取序列化对象
主要是从 HttpServletRequest 提取相关的信息,也就是提取 HttpServletRequest 中的
Remotelnvocation 对象的序列化信息以及反序列化的过程
执行调用
根据反序列化方式得到的 Remotelnvocation 对象中的信息,进行方法调用 注意, 此时调
用的实体并不是服务接口或者服务类,而是之前在初始化时候构造的封装了服务接口以及服务
类的代理
完成了 Remotelnvocation 实例的提取, 也就意味着可以通过 Remotelnvocation 实例中提供
的信息进行方法调用了
这段函数有两点需要说明的地方
对应方法的激活也就是 invoke 方法的调用,虽然经过层层环绕,但是最终还是实现了
一个我们熟知的调用 invocation.invoke(targetObject),也就是执行 Remotelnvocation 类
中的 invoke 方法,大致的逻辑还是通过 Remotelnvocation 中对应的方法信息在
targetObject 上去执行,此方法在分析 RMI 功能的时候已经分析过,不再赘述 但是
在对于当前方法的 targetObject 参数,此 targetObject 是代理类,调用代理类的时候需
妥考虑增强方 法的调用,这是读者需要注意的地方
对于返回结果需要使用 RemotelnvocationResult 进行封装,之所以需要通过使用
RemotelnvocationResult 类进行封装,是 因为无法保证对于所有操作的返回结果都继承
Serializable 接口,也就是说无法保证所有返回结果都可以直接进行序列化,那么,就
必须使用 RemotelnvocationResult 类进行统一封装
将结果的序列化对象写入输出流
同样这里也包括结果的序列化过程
客户端实现
在服务端调用
的分析中反复提到需要从 HttpServletRequest 中提取从客户端传来的 Remotelnvocation 实
例,然后进行相应解析 所以,在客户端,一个比较重要的任务就是构建 Remotelnvocati 实
例,并传送到服务端 根据配置文件中的信息,我们还是首先锁定 HttplnvokerProxyFactoryBean
类,并查看其层次结构
HttpInvokerProxy FactoryBean 类同样实现了 InitializingBean 接口
同时 ,又实现了 FactoryBean 以及 Methodlnterceptor,根据实现的
InitializingBean 接口分析初始化过程中的逻辑
在 afterPropertiesSet 中主要创建了一个代理,该代理封装了配置的服务接口 并使用当前
类也就是 HttplnvokerProxyFactoryBean 作为增强 因为 HttplnvokerProxyFactoryBean 实现了
Methodlnterceptor 方法, 所以可以作为增强拦截器
由于 HttpinvokerProxyFactoryBean 实现了 FactoryBean 接口 ,所以通过 Spring 中
普通方式调用该 bean 时调用的并不是该 bean 本身,而是此类中 getObject 方法返回的实例,也
就是实例化过程中所创建的代理
HttplnvokerProxyFactoryBean 类型 bean
在初始化过程中创建了封装服务接口的代理 并使用自身作为增强拦截器,然后又因为实现了
FactoryBean 接口,所以获取 Bean 的时候返回的其实是创建的代理 那么,汇总上面的逻辑
当调用如下代码时 ,其实是调用代理类中的服务方法,而在调用代理类中的服务方法时又会使
用代理类中加入的增强器进行增强
这时, 所有的逻辑分析其实已经被转向了对于增强器也就是 HttplnvokerProxyFactoryBean
类本身的 invoke 方法的分析
在分析 invoke 方法之前,其实已经猜出了该方法所提供的主要功能就是将调用信息封
装在 Remotelnvocation 中, 发送给服务端并等待 返回结果
函数主要有 3 个步骤
构建 Remotelnvocation 实例
远程执行方法
提取结果
而在这 3 个步骤中最为关键的就是远程方法的执行 执行远程调用的首要步骤就是将调用
方法的实例写入输出流中
在doExecuteRequest 方法中真正实现了对远程方法的构造与通信,与远程方法的连接功能
实现中, Spring 引入了第三方 JAR: HttpClient HttpClient 是 Apache Jakarta Common 下的子项
目 可以用来提供高效的、最新的 功能丰富的支持 HTTP 协议的客户端编程工具包 并且它
支持 HTTP 协议最新的版本和建议
客户端实现的逻辑
创建 HttpPost
由于对于服务端方的调用是通过 Post 方式进行的,那么首先要做的就是构建 HttpPost
构建 HttpPost 过程中可以设置一些必要的参数。
设置 RequestBody
构建好 PostMethod 实例后便可以将存储 Remotelnvocation 实例的序列化对象的输出流设置进
去,当然这里需要注意的是传人的 ContentType 类型,一定要传入 application/x-java-serialized-object
以保证服务端解析时会按照序列化对象的解析方式进行解析
执行远程方法
通过 HttpClient 所提供的方法来直接执行远程方法
远程相应验证
对于 HTTP 调用的响应码处理,大于 300 则是非正常调用的响应码
提取晌应信息
从服务器返回的输入流可能是经过压缩的,不同的方式采用不同的办法进行提前
提取返回结果
提取结果的流程主要是从输入流中提取响应的序列化信息



