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

获取registry

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

  1. protected Registry getRegistry(String registryHost, int registryPort,
  2. RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)
  3. throws RemoteException {
  4. if (registryHost != null) {
  5. // Host explicitly specified: only lookup possible.
  6. if (logger.isInfoEnabled()) {
  7. logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
  8. }
  9. //如果registryHost不为空则尝试获取对应主机的Registry
  10. Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
  11. //远程连接测试
  12. testRegistry(reg);
  13. return reg;
  14. }
  15. else {
  16. //获取本机的Registry
  17. return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
  18. }
  19. }

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

  1. protected Registry getRegistry(
  2. int registryPort, RMIClientSocketFactory clientSocketFactory, RMIServerSocketFactory serverSocketFactory)
  3. throws RemoteException {
  4. if (clientSocketFactory != null) {
  5. if (this.alwaysCreateRegistry) {
  6. logger.info("Creating new RMI registry");
  7. //使用clientSocketFactory创建Registry
  8. return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
  9. }
  10. if (logger.isInfoEnabled()) {
  11. logger.info("Looking for RMI registry at port '" + registryPort + "', using custom socket factory");
  12. }
  13. synchronized (LocateRegistry.class) {
  14. try {
  15. // Retrieve existing registry.
  16. //复用测试
  17. Registry reg = LocateRegistry.getRegistry(null, registryPort, clientSocketFactory);
  18. testRegistry(reg);
  19. return reg;
  20. }
  21. catch (RemoteException ex) {
  22. logger.debug("RMI registry access threw exception", ex);
  23. logger.info("Could not detect RMI registry - creating new one");
  24. // Assume no registry found -> create new one.
  25. return LocateRegistry.createRegistry(registryPort, clientSocketFactory, serverSocketFactory);
  26. }
  27. }
  28. }
  29. else {
  30. return getRegistry(registryPort);
  31. }
  32. }
  33. //如果创建Registry实例时不需要使用自定义的套接字工厂,那么就可以直接使用LocateRegistry.createRegistry(...)方法来创建了,当然复用的检测还是必要的。
  34. protected Registry getRegistry(int registryPort) throws RemoteException {
  35. if (this.alwaysCreateRegistry) {
  36. logger.info("Creating new RMI registry");
  37. return LocateRegistry.createRegistry(registryPort);
  38. }
  39. if (logger.isInfoEnabled()) {
  40. logger.info("Looking for RMI registry at port '" + registryPort + "'");
  41. }
  42. synchronized (LocateRegistry.class) {
  43. try {
  44. // Retrieve existing registry.
  45. //查看对应当前registryPort的Registry是否已经创建,如果创建直接使用
  46. Registry reg = LocateRegistry.getRegistry(registryPort);
  47. //测试是否可用,如果不可用则抛出异常
  48. testRegistry(reg);
  49. return reg;
  50. }
  51. catch (RemoteException ex) {
  52. logger.debug("RMI registry access threw exception", ex);
  53. logger.info("Could not detect RMI registry - creating new one");
  54. // Assume no registry found -> create new one.
  55. //根据端口创建Registry
  56. return LocateRegistry.createRegistry(registryPort);
  57. }
  58. }
  59. }

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

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

  1. protected Remote getObjectToExport() {
  2. // determine remote object
  3. //如果配置的service属性对应的类实现了Remote接口且没有配置serviceInterface属性
  4. if (getService() instanceof Remote &&
  5. (getServiceInterface() == null || Remote.class.isAssignableFrom(getServiceInterface()))) {
  6. // conventional RMI service
  7. return (Remote) getService();
  8. }
  9. else {
  10. // RMI invoker
  11. if (logger.isDebugEnabled()) {
  12. logger.debug("RMI service [" + getService() + "] is an RMI invoker");
  13. }
  14. //对service进行封装
  15. return new RmiInvocationWrapper(getProxyForService(), this);
  16. }
  17. }

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

  1. protected Object getProxyForService(){
  2. //验证service
  3. checkService();
  4. //验证serviceInterface
  5. checkServiceInterface();
  6. //使用JDK的方式创建代理
  7. ProxyFactory proxyFactory = new ProxyFactory();
  8. //添加代理接口
  9. proxyFactory.addInterface(getServiceInterface());
  10. if(registerTraceInterceptor == null ? interceptors == null : registerTraceInterceptor.booleanValue())
  11. //加入代理的横切面RemoteInvocationTraceInterceptor并记录Exporter名称
  12. proxyFactory.addAdvice(new RemoteInvocationTraceInterceptor(getExporterName()));
  13. if(interceptors != null)
  14. {
  15. AdvisorAdapterRegistry adapterRegistry = GlobalAdvisorAdapterRegistry.getInstance();
  16. for(int i = 0; i < interceptors.length; i++)
  17. proxyFactory.addAdvisor(adapterRegistry.wrap(interceptors[i]));
  18.  
  19. }
  20. //设置要代理的目标类
  21. proxyFactory.setTarget(getService());
  22. proxyFactory.setOpaque(true);
  23. //创建代理
  24. return proxyFactory.getProxy(getBeanClassLoader());
  25. }

RMI服务激活调用

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

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

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

  1. protected Object invoke(RemoteInvocation invocation, Object targetObject)
  2. throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  3. return super.invoke(invocation, targetObject);
  4. }
  5. protected Object invoke(RemoteInvocation invocation, Object targetObject) throws NoSuchMethodException,
          IllegalAccessException, InvocationTargetException {
  6. if(logger.isTraceEnabled())
  7. logger.trace((new StringBuilder()).append("Executing ").append(invocation).toString());
  8. try {
  9. return getRemoteInvocationExecutor().invoke(invocation, targetObject);
  10. }catch(NoSuchMethodException ex){
  11. if(logger.isDebugEnabled())
  12. logger.warn((new StringBuilder())
                        .append("Could not find target method for ")
                        .append(invocation).toString(), ex);
  13. throw ex;
  14. }
  15. catch(IllegalAccessException ex){
  16. if(logger.isDebugEnabled())
  17. logger.warn((new StringBuilder())
                        .append("Could not access target method for ")
                        .append(invocation).toString(), ex);
  18. throw ex;
  19. }catch(InvocationTargetException ex){
  20. if(logger.isDebugEnabled())
  21. logger.debug((new StringBuilder()).append("Target method failed for ")
                      .append(invocation).toString(), ex.getTargetException());
  22. throw ex;
  23. }
  24. }
  25. public Object invoke(RemoteInvocation invocation, Object targetObject)
  26. throws NoSuchMethodException, IllegalAccessException, InvocationTargetException{
  27. Assert.notNull(invocation, "RemoteInvocation must not be null");
  28. Assert.notNull(targetObject, "Target object must not be null");
  29. //通过反射方式激活方法
  30. return invocation.invoke(targetObject);
  31. }
  32. public Object invoke(Object targetObject)
  33. throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
  34. //根据方法名称获取代理中的方法
  35. Method method = targetObject.getClass().getMethod(methodName, parameterTypes);
  36. //执行代理中方法
  37. return method.invoke(targetObject, arguments);
  38. }

targetObject为之前封装的代理类。

SpringRMI解析3-RmiServiceExporter逻辑细节的更多相关文章

  1. SpringRMI解析2-RmiServiceExporter逻辑脉络

    配置文件是Spring的核心,在配置文件中我们可以看到,定义了两个bean,其中一个是对接口实现类的发布,而另一个则是对RMI服务的发布,使用org.springframework.remoting. ...

  2. SpringMVC解析5-DispatcherServlet逻辑细节

    MultipartContent类型的request处理 对于请求的处理,spring首先考虑的是对于Multipart的处理,如果是MultipartContent类型的request,则转换req ...

  3. SpringRMI解析1-使用示例

    Java远程方法调用,即JavaRMI(JavaRemote Method Invocation),是Java编程语言里一种用于实现远程过程调用的应用程序编程接口.它使客户机上的运行的程序可以调用远程 ...

  4. SpringRMI解析4-客户端实现

    根据客户端配置文件,锁定入口类为RMIProxyFactoryBean,同样根据类的层次结构查找入口函数. <bean id="rmiServiceProxy" class= ...

  5. yolo源码解析(1):代码逻辑

    一. 整体代码逻辑 yolo中源码分为三个部分,\example,\include,以及\src文件夹下都有源代码存在. 结构如下所示 ├── examples │ ├── darknet.c(主程序 ...

  6. APP注册&登陆 逻辑细节

    前言:有多少用户愿意注册登陆,决定了一款产品的最大活跃度. 用户登陆注册系统分为两大类: 自建用户系统:邮箱/手机号/用户名/二维码/人脸识别/指纹 第三方授权用户系统:微信/微博/支付包/豆瓣/Fa ...

  7. 自己动手实现一个简单的JSON解析器

    1. 背景 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.相对于另一种数据交换格式 XML,JSON 有着诸多优点.比如易读性更好,占用空间更少等.在 ...

  8. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  9. Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

随机推荐

  1. JSON数据格式

    JSON 数据格式 JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式.JSON采用完全独立于语言的文本格式,这些特性使JSON成为理想的数据交换语言.易于人 ...

  2. s:iterator,s:if与OGNL的嵌套使用

    今天在写代码时,遇到个如下问题,要求当前登陆用户的id与系统参数类型代码所属维护人的id相同时,显示单选框.如下效果: 代码如下: <s:iterator value="vo.page ...

  3. MyEclipse/Eclipse中修改包的显示结构

    操作如下:

  4. 73. Set Matrix Zeroes

    题目: Given a m x n matrix, if an element is 0, set its entire row and column to 0. Do it in place. Fo ...

  5. linux crontab 学习

    安装crontab:[root@CentOS ~]# yum install vixie-cron[root@CentOS ~]# yum install crontabs/sbin/service ...

  6. ASP.NET SignalR 与 LayIM2.0 配合轻松实现Web聊天室(四) 之 用户搜索(Elasticsearch),加好友流程(1)。

    前面几篇基本已经实现了大部分即时通讯功能:聊天,群聊,发送文件,图片,消息.不过这些业务都是比较粗犷的.下面我们就把业务细化,之前用的是死数据,那我们就从加好友开始吧.加好友,首先你得知道你要加谁.L ...

  7. Mysql控制语句

    14.6.5.1 CASE Syntax 14.6.5.2 IF Syntax 14.6.5.3 ITERATE Syntax 14.6.5.4 LEAVE Syntax 14.6.5.5 LOOP ...

  8. timestamp 类型的索引

    由这条语句datetime.strftime('2014/12/05','%Y/%m/%d')转换出来的索引 是pandas内置类型相同,如果使用datetime.strftime('2014/12/ ...

  9. FragmentPagerAdapter实现刷新

    在fragmentpageadapter的instantiateItem方法里,他会先去FragmentManager里面去查找有没有相关的fragment如果有就直接使用如果没有才会触发fragme ...

  10. set -x 跟踪脚本执行信息

    注意set -x其中"-"与"x"之间没有空格 [root@GitLab sh]# ./sx.sh heelo + a=heelo + echo heelo h ...