zoukankan      html  css  js  c++  java
  • java RMI原理详解

      java本身提供了一种RPC框架——RMI(即Remote Method Invoke 远程方法调用),在编写一个接口需要作为远程调用时,都需要继承了Remote,Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口,只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用,下面通过一个简单的示例,来讲解RMI原理以及开发流程:

      为了真正实现远程调用,首先创建服务端工程rmi-server,结构如下:

      

      代码说明:

      1.User.java:用于远程调用时pojo对象的传输,该对象必须实现Serializable接口,否则在调用过程中,会抛出NotSerializableException异常,代码如下:

    /**
     * 用户信息,用于远程调用传输,必须实现Serializable接口
     * 
     * @author andy
     *
     */
    public class User implements Serializable {
        private static final long serialVersionUID = 1L;
    
        private String name;
    
        private int age;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return "name : " + this.name + ", age : " + this.age;
        }
    }

       2.IHello.java:远程接口,该接口需要继承Remote接口,并且接口中的方法全都要抛出RemoteException异常,代码如下:

    import java.rmi.Remote;
    import java.rmi.RemoteException;
    
    import pers.andy.rmi.bean.User;
    
    /**
     * 定义一个远程接口,必须继承Remote接口,其中需要远程调用的方法必须抛出RemoteException异常
     * 
     * @author andy
     *
     */
    public interface IHello extends Remote {
    
        /**
         * 更新user信息
         * @param user
         * @return
         * @throws RemoteException
         */
        public User updateUser(User user) throws RemoteException;
    }

       3.HelloImpl:远程接口实现类,必须继承UnicastRemoteObject(继承RemoteServer->继承RemoteObject->实现Remote,Serializable),只有继承UnicastRemoteObject类,才表明其可以作为远程对象,被注册到注册表中供客户端远程调用(补充:客户端lookup找到的对象,只是该远程对象的Stub(存根对象),而服务端的对象有一个对应的骨架Skeleton(用于接收客户端stub的请求,以及调用真实的对象)对应,Stub是远程对象的客户端代理,Skeleton是远程对象的服务端代理,他们之间协作完成客户端与服务器之间的方法调用时的通信。,代码如下:

    /**
     * 远程的接口的实现,继承了UnicastRemoteObject,表明该类作为一个远程对象
     * 
     * @author andy
     *
     */
    public class HelloImpl extends UnicastRemoteObject implements IHello {
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
    
        /**
         * 因为UnicastRemoteObject的构造方法抛出了RemoteException异常,因此这里默认的构造方法必须写,必须声明抛出RemoteException异常
         * 
         * @throws RemoteException
         */
        public HelloImpl() throws RemoteException {
        }
    
        public User updateUser(User user) throws RemoteException {
            System.out.println("-------------- 客户端发送的user为" + user.toString());
            user.setName("andy2");
            user.setAge(30);
            return user;
        }
    }

      4.HelloServer:服务端启动类,用于创建远程对象注册表以及注册远程对象,代码如下:

    /**
     * 服务端启动类
     * 
     * @author andy
     *
     */
    public class HelloServer {
        public static void main(String args[]) {
            try {
           // 本地主机上的远程对象注册表Registry的实例,并指定端口为8888,这一步必不可少(Java默认端口是1099)
                LocateRegistry.createRegistry(8888);
                // 把远程对象注册到RMI注册服务器上,并命名为RHello
                // 绑定的URL标准格式为:rmi://host:port/name(其中协议名可以省略,下面两种写法都是正确的)
                Naming.bind("rmi://localhost:8888/RHello", rhello);
                // Naming.bind("//localhost:8888/RHello",rhello);
                System.out.println("------------远程对象IHello注册成功,等待客户端调用...");
            } catch (RemoteException e) {
                System.out.println("创建远程对象发生异常!");
                e.printStackTrace();
            } catch (AlreadyBoundException e) {
                System.out.println("发生重复绑定对象异常!");
                e.printStackTrace();
            } catch (MalformedURLException e) {
                System.out.println("发生URL畸形异常!");
                e.printStackTrace();
            }
        }
    }

      补充说明:为何HelloImpl继承了UnicastRemoteObject就可以被作为远程对象发布,查阅UnicastRemoteObject的源码可以发现:

        protected UnicastRemoteObject() throws RemoteException
        {
            this(0);
        }
        protected UnicastRemoteObject(int port) throws RemoteException
        {
            this.port = port;
            exportObject((Remote) this, port);
        }

      其实在启动server端的时候,new了HelloImpl对象,因为继承了UnicastRemoteObject,会先调用父类的构造方法,这时候,就会将this(当前对象)通过exportObject方法注册。

      所以,如果在被导出的对象需要继承其它的类,那么就可以不采用集成UnicastRemoteObject的方式,而是通过exportObject方法将其导出为远程对象:

    ...
    // 创建一个远程对象
    IHello rhello = new HelloImpl();
    //HelloImpl不需要继承UnicastRemoteObject类,通过exportObject将其显示导出
    UnicastRemoteObject.exportObject(rhello,0);
    ...

      以上即是服务端所有代码,接下来是创建客户端工程,结构如下:

      

      实际应用开发中,客户端的User.java和IHello.java应该是从服务端导出jar包的形式添加到依赖库里,因此这边只介绍HelloClient.java,该类为客户端启动类,用于在注册表中查找远程对象实现远程方法调用,代码如下:

    /**
     * 客户端启动类
     * 
     * @author andy
     *
     */
    public class HelloClient {
        public static void main(String args[]) {
            try {
                // 在RMI服务注册表中查找名称为RHello的对象,并调用其上的方法
                IHello rhello = (IHello) Naming.lookup("rmi://localhost:8888/RHello");
           // 构造user对象,测试远程对象传输 User user = new User(); user.setAge(20); user.setName("andy"); System.out.println("-------------- 服务端返回的的user为" + rhello.updateUser(user).toString()); } catch (NotBoundException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (RemoteException e) { e.printStackTrace(); } } }

       到此为止,客户端和服务端的工程都搭建完毕,现在可以进行测试,执行次序和测试结果如下所示:

      1.首先运行服务端启动类HelloServer,结果如下:

      服务端:------------远程对象IHello注册成功,等待客户端调用...

      2.运行客户端启动类,结果如下:

      服务端:-------------- 客户端发送的user为name : andy, age : 20

      客户端:-------------- 服务端返回的的user为name : andy2, age : 30

      

  • 相关阅读:
    AKKA学习(二) 未完
    AKKA学习(一)
    seata项目结构
    seata demo
    FESCAR
    GTS原理、架构
    Fescar使用(资料)
    高性能异步分布式事务TCC框架(资料汇总)
    TIDB学习资料
    自旋锁
  • 原文地址:https://www.cnblogs.com/handsomeye/p/6514026.html
Copyright © 2011-2022 走看看