RMI全面了解

RMI的入门

一 .RMI概述
RMI(Remote Method Invocation)
    RMI是分布式对象软件包,它简化了在多台计算机上的JAVA应用之间的通信。必须在jdk1.1以上
RMI用到的类
     java.rmi.Remote                   所有可以被远程调用的对象都必须实现该接口
     java.rmi.server.UnicastRemoteObject  所有可以被远程调用的对象都必须扩展该类
 
什么是RMI
    远程方法调用是一种计算机之间对象互相调用对方函数,启动对方进程的一种机制,
使用这种机制,某一台计算机上的对象在调用另外一台计算机上的方法时,使用的程
序语法规则和在本地机上对象间的方法调用的语法规则一样。

优点
这种机制给分布计算的系统设计、编程都带来了极大的方便。
只要按照RMI规则设计程序,可以不必再过问在RMI之下的网络细节了,如:TCP和Socket等等。
任意两台计算机之间的通讯完全由RMI负责。调用远程计算机上的对象就像本地对象一样方便。
 
1、面向对象:
RMI可将完整的对象作为参数和返回值进行传递,而不仅仅是预定义的数据类型。
也就是说,可以将类似Java哈西表这样的复杂类型作为一个参数进行传递。
 
2、可移动属性:
RMI可将属性从客户机移动到服务器,或者从服务器移动到客户机。
 
3、设计方式:
对象传递功能使您可以在分布式计算中充分利用面向对象技术的强大功能,如二层和三层结构系统。
如果用户能够传递属性,那么就可以在自己的解决方案中使用面向对象的设计方式。
所有面向对象的设计方式无不依靠不同的属性来发挥功能,如果不能传递完整的对象——包括实现和类型
——就会失去设计方式上所提供的优点。
 
4、安全性:
RMI使用Java内置的安全机制保证下载执行程序时用户系统的安全。
RMI使用专门为保护系统免遭恶意小程序侵害而设计的安全管理程序。
5、便于编写和使用
RMI使得Java远程服务程序和访问这些服务程序的Java客户程序的编写工作变得轻松、简单。
远程接口实际上就是Java接口。
为了实现RMI的功能必须创建远程对象任何可以被远程调用的对象必须实现远程接口。但远程
接口本身并不包含任何方法。因而需要创建一个新的接口来扩展远程接口。
新接口将包含所有可以远程调用的方法。远程对象必须实现这个新接口,由于新的接口扩展了
远程接口,实现了新接口,就满足了远程对象对实现远程接口的要求,所实现的每个对象都将
作为远程对象引用。
 
个人总结:
    RMI说白了,就是提供了一种远程的方法调用。 这种调用简单方便,可以传递复杂java对象。现在流行的j2ee中的EJB的底层实现技术就是RMI,EJB的调用就是经过封装的,更高级的RMI调用。

下面我们就来写一个RMI的程序:
 
一.创建RMI程序的6个步骤:
1、定义一个远程接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常。
2、定义一个实现该接口的类。
3、使用RMIC程序生成远程实现所需的残根和框架。
4、创建一个服务器,用于发布2中写好的类。
5. 创建一个客户程序进行RMI调用。
6、启动rmiRegistry并运行自己的远程服务器和客户程序。
 
二. 程序详细说明
 
1.定义一个远程接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常。
 
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface I_Hello extends java.rmi.Remote   //需要从Remote继承
{
       public String SayHello() throws RemoteException;   //需要抛出remote异常
}
 
   上面例子我们定义一个返回字符串的远程方法 SayHello(),这个远程接口 I_Hello必须是public的 ,它必须从java.rmi.Remote继承而来,接口中的每一个方法都必须抛出远程异常java.rmi.RemoteException。

抛出这个异常的原因
由于任何远程方法调用实际上要进行许多低级网络操作,因此网络错误可能在调用过程中随时发生。
因此,所有的RMI操作都应放到try-catch块中。
  
2、定义一个实现该接口的类。
 
 import java.io.PrintStream;
import java.rmi.*;
import java.rmi.server.UnicastRemoteObject;
 
public class Hello extends UnicastRemoteObject   //必须从UnicastRemoteObject  继承
                   implements I_Hello
{
        public Hello() throws RemoteException     //需要一个抛出Remote异常的默认初始化方法
        {
        }
 
        public String SayHello()     //这个是实现I_Hello接口的方法
        {
           return "Hello world !!";
        }
}
 
实现接口的类必须继承UnicastRemoteObject类。
扩展java.rmi.server.UnicastRemoteObject
UnicastRemoteObject顾名思义,是让客户机与服务器对象实例建立一对一的连接。
 
3、使用RMIC程序生成远程实现所需的残根Stub 和 框架。
   2中的Hello 编译好以后,我们就可以用RMIC命令来生成残根Stub
   在Dos窗口里,到Hello.class 所在目录,运行以下命令:
   rmic Hello
   
   命令执行完以后,将会在当前目录生成一个 Hello_Stub.class 这个就是我们远程调用时需要的类
 
参考:
在RMI中,客户机上生成的调动调用参数和反调动返回值的代码称为残根。有的书上称这部分代码为“主干”。
服务器上生成的反调动调用参数和进行实际方法调用调动返回值的代码称为框架。
生成残根和框架的工具
Rmic命令行工具(RMI Compiler)
格式:
Rmic classname
 
4、创建一个服务器,用于发布2中写好的类。
 
    import java.rmi.*;
public class RMI_Server
{
    public static void main(String[] args)
    {
        try
        {
            Hello hello = new Hello();                //实例化要发布的类
            Naming.rebind("RMI_Hello", hello);      //绑定RMI名称 进行发布
            System.out.println("=== Hello server Ready === ");
        }
        catch(Exception exception)
        {
            exception.printStackTrace();
        }
    }
}
 
5. 创建一个客户程序进行RMI调用。
 
import java.rmi.*;
public class RMI_Client {
    public static void main(String[] args) {
        try
        {
           I_Hello hello = (I_Hello) Naming.lookup("RMI_Hello");  //通过RMI名称查找远程对象
            System.out.println(hello.SayHello());                        //调用远程对象的方法
        } catch (Exception e)
        {
          e.printStackTrace();
        }
    }

}
 
Naming.lookup("RMI_Hello") 其中的参数“RMI_Hello”只是针对本机的RMI查找,如果是异地的RMI调用请参照  rmi://127.0.0.1:1099/RMI_Hello       端口1099是默认的RMI端口,如果你启动 rmiregistry 的时候(见第6点)没有指定特殊的端口号,默认就是1099
 
到此 我们 所有的代码编写都完成了,不过不要急着去运行,请跟随第6点去运行,因为rmi 调用还会遇到一些特别的情况,偶花了牛劲,才找到原因的,许多刚用RMI的人,常常被这些问题搞得吐血
 
6、启动rmiRegistry并运行自己的远程服务器和客户程序。
 1)服务器的运行
    先在DOS下运行 rmiregistry     这个命令是开启RMI的注册服务,开启以后我们的server程序才能调用rebing方法发布我们的类
 
    然后,运行我们的server程序  RMI_Server    这里是最容易出错的,参见下面注意事项。
     注意:
         如果提示找不到Stub类,这个需要用下面的命令来运行
 java.exe -Djava.rmi.server.codebase=file:/ E:/MIS_Interface/momo/TestEasy/classes/  RMI_Server
 
蓝字部分指定了stub类的路径。
 
  有人会问,我已经把stub 通过-classpath 加到类路径里面了,为什么还没有提示这个错误呢?原因是这样的: 这里提示的找不到stub类,不是由你写的RMI_Server这个程序引起的,是由rmi注册服务器报告的异常,也就是我们前面启动的 rmiregistry ,因为你写的RMI_Server 要求RMI注册服务器注册一个新的类,自然RMI服务器必须知道你的类放在哪里,所以我们通过  -Djava.rmi.server.codebase 这个运行参数来指定
  你也可以通过修改操作系统的classpath 环境变量 来指定stub的位置,只不过太麻烦
 
2) 客户端的运行
      直接运行RMI_Client  即可  注意 把 Stub 和 接口 I_Hello 加到类路径里
  
    通常第一次运行 客户端都会报一个错误:   Access  XXXX 不记得具体的了,反正就是“访问权限限制”, 这是因为RMI的服务需要授权,外部程序才能访问,所以我们要改动 jre的安全配置文件,来开放权限,  具体如下:
 
   打开你的jdk目录下的这个文件 C:/Program Files/Java/jdk1.5.0_04/jre/lib/security/java.policy
在文件最后加入下面代码:
 grant {
           permission java.net.SocketPermission "*:1024-65535",
                "connect,accept";
           permission java.net.SocketPermission "*:80","connect";
        };
此代码,开放了端口的connect访问权限
 
注意 你应该修改服务器那台机子的安全配置文件,也就是你运行 rmiregistry 和 RMI_Server的机子
另外,很多人修改完以后,仍然报这个错误,多数情况是由于你没有修改到正确的jdk 下的文件,而是修改到其他jdk的文件, 我们安装oracle , Weblogic等等软件的时候都会自带一个 jdk,他们会自动在操作系统的环境变量里面 加入jdk的路径,所以,你先要确定你运行服务器端程序是用哪个jdk,再修改这个jdk下的配置文件,确定当前jdk的路径很简单  开始 -》运行-》rmiregistry 看看这个DOS窗口标题 的路径,就是你当前系统默认jdk的路径了
 
客户端正常运行以后,就会出现以下结果:
Hello world !!
 
这些字符是通过RMI调用远程服务器的类返回的结果

 

RMI的使用介绍

远程方法调用正是基于三层架构设计的中间层。远程方法调用的原理是桩和骨架结构。主要有三层,分别是桩/骨架层,远程引用层,还有传输层。
    要完成一个简单的RMI应用要分为以下几个简单的步骤:
    1。定义远程接口
    2。定义和实现RMI服务器类
    3。定义和实现客户端类
    4。编译以上的源码
    5。生成桩/骨架
    6。创建安全策略
    7。启动RMI注册表
    8。启动服务器
    9。启动客户端
 
我们来做一个例子,在服务器上有一个连接数据库,插入值的方法,我们在客户端要使用这个方法,来真正的插入一个值!
    第一步:先编写一个接口AccountServer.java,这个接口要继承Remote接口,并且提供一个远程方法insertDetails(),这个方法要抛出RemoteException异常,代码如下:

    import java.rmi.Remote;
import java.rmi.RemoteException;

public interface AccountServer extends Remote
{
    public String insertDetails(String firstName, String lastName,
            String phone, String income, String accountType)
            throws RemoteException;
}

    第二步:实现远程对象类,就是RMI服务器,AccountServerImpl.java,这个类要继承UnicastRemoteObject类,并且要实现自己写的远程接口类AccountServer,首先要定义远程对象构造器,然后就是要实现远程接口中的方法,这个方法来连接mysql数据库,并且插入值.然后我们main方法中,设置安全管理程序,然后创建远程对象,接着注册远程对象.代码如下:

import java.rmi.Naming;
import java.rmi.RMISecurityManager;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;

public class AccountServerImpl extends UnicastRemoteObject implements
        AccountServer
{
    private Connection con;

    public AccountServerImpl() throws RemoteException
    {
        super();
    }

    public String insertDetails(String firstName, String lastName,
            String phone, String income, String accountType)
            throws RemoteException
    {
        int rowsAffected = 0;
        String sReturn = "fail";
        try
        {
            Class.forName("com.mysql.jdbc.Driver");
            con = DriverManager
                    .getConnection(
                            "jdbc:mysql://127.0.0.1/jsptest?useUnicode=true&characterEncoding=GBK",
                            "root", "");
            PreparedStatement ps = con
                    .prepareStatement("insert into User_info values(?,?,?,?,?)");
            ps.setString(1, "firstName");
            ps.setString(2, "lastName");
            ps.setString(3, "phone");
            ps.setString(4, "income");
            ps.setString(5, "accountType");

            rowsAffected = ps.executeUpdate();

            if (rowsAffected > 0)
            {
                sReturn = "success";
            }

        }
        catch (Exception e)
        {
            e.printStackTrace();           
        }
        return sReturn;
    }

    public static void main(String[] args)
    {
 //设置安全管理程序
        System.setSecurityManager(new RMISecurityManager());

        try
        {
     //创建远程对象
            AccountServerImpl as = new AccountServerImpl();
            //注册远程对象
            Naming.rebind("AccountServer", as);
            System.out.println("RMI服务器启动成功");
        }
        catch (RemoteException e)
        {
            e.printStackTrace();
        }
        catch (Exception e1)
        {
            e1.printStackTrace();
        }
    }

}
       第三步:编写客户端代码,Client.java 这个代码中有两个重要的任务,就是得到远程对象的应用,然后调用远程对象,其余的都是界面部分,就不再讲解,代码如下:

import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.rmi.Naming;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class Client
{
    public static void main(String[] args)
    {
        ClientFrame frame = new ClientFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.show();
    }
}

class ClientFrame extends JFrame
{
    public ClientFrame()
    {
        setTitle("客户端");
        setSize(200, 220);

        Container con = getContentPane();
        ClientPanel panel = new ClientPanel();
        con.add(panel);
    }
}

class ClientPanel extends JPanel
{
    private JLabel lfirst, llast, lphone, lincome, ltype;

    private JTextField ffirst, flast, fphone, fincome, ftype;

    private JButton submit;

    public ClientPanel()
    {
        lfirst = new JLabel("   姓:");
        llast = new JLabel("   名:");
        lphone = new JLabel("电话:");
        lincome = new JLabel("来自:");
        ltype = new JLabel("类型:");

        ffirst = new JTextField(10);
        flast = new JTextField(10);
        fphone = new JTextField(10);
        fincome = new JTextField(10);
        ftype = new JTextField(10);

        submit = new JButton(" 提  交 ");
        submit.addActionListener(new ActionListener()
        {
            public void actionPerformed(ActionEvent e)
            {
                try
                {
                    //得到远程对象
                    AccountServer as = (AccountServer) Naming
                            .lookup("rmi://localhost/AccountServer");
                    String firstName = ffirst.getText();
                    String lastName = flast.getText();
                    String phone = fphone.getText();
                    String income = fincome.getText();
                    String accountType = ftype.getText();
             //调用远程方法
                    String str = as.insertDetails(firstName, lastName, phone,
                            income, accountType);
                    if ("fail".equals(str))
                    {
                        JOptionPane.showMessageDialog(null, "插入失败");
                    }
                    else
                    {
                        JOptionPane.showMessageDialog(null, "插入成功");
                    }

                }
                catch (Exception e2)
                {
                    e2.printStackTrace();
                }
            }
        });

        add(lfirst);
        add(ffirst);
        add(llast);
        add(flast);
        add(lphone);
        add(fphone);
        add(lincome);
        add(fincome);
        add(ltype);
        add(ftype);
        add(submit);
    }
}

       第四步:在Dos下,进入代码所在目录,使用javac *.java 来编译所有代码
       第五步:在class文件所在目录使用 rmic AccountServerImpl来生成桩和骨架(AccountServerImpl_Skel.class和AccountServerImpl.Stub.class)
       第六步:用policytool工具来创建安全策略文件,放开所有的权限,保存.java.policy文件在C:/Documents and Settings/Administrator文件夹下
       第七步:使用start rmiregistry命令来启动注册表,默认使用1099端口,我们不需要改变
       第八步:java -Djava.rmi.server.codebase=file:/e:/aa/ -classpath .;%CLASSPATH% AccountServerImpl来启动服务器,注意由于服务器中要使用Mysql数据库,所有我们引入使用数据库的jar包,将mysql的jar包添加的CLASSPATH,就OK呢
       第九步:使用java Client来启动客户端,和得到图形界面的客户端,填入数据,点击提交,数据就会添加进入数据库

附:
数据库名称:jsptest
表名:user_info
表结构:
CREATE TABLE user_info (
  firstName varchar(50) NOT NULL default '',
  lastName varchar(50) NOT NULL default '',
  phone varchar(20) NOT NULL default '',
  income varchar(30) NOT NULL default '',
  accountType varchar(50) NOT NULL default '',
  PRIMARY KEY  (firstName)
)

 

RMI代码动态下载 

我看网上讲RMI应用时都是些比较老的文章,从前部署一个RMI应用有6个步骤:
(1)定义和实现远端接口中的参数
(2)定义和实现远端接口
(3)编写服务端代码
(4)编写客户端代码
(5)生成stub和skeltion ,并将stub打包到客户端jar中,将skeltion打包到服务端jar中
(6)启动rmiregistry , 并将服务注册到rmiregistry中,然后运行代码。
上边的步骤涉及到了好几个长串复杂的命令。。。
但在JDK5中使用了动态代理技术,实现了动态生成stub和skeltion类,从而省却了相当多的繁琐工作。(我这还是后来知道,我是说我这么简单就实现了RMI,跟网上说的咋区别那大。。。)

如此一来RMI成了相当轻量级的技术了,但还是会遇到繁琐的事情,比如代码动态下载,动态下载允许RMI应用动态从http后者ftp下载类定义的字节码(运行时加入参数-Djava.rmi.server.codebase=http://***/*.jar),就像是把远程的类加入本地的classpath一样,相当方便的技术。问题就是如果我们只是学习或做个简单的应用,为了部署一个RMI还要启用一个http或ftp服务,多么麻烦,谁都想只用启动程序就可以了,尤其是在调试的时候。当然如果只是在本地调试的话,可以用file://代替http,可是如果是个简单的应用必须用到http服务,但又不想兴师动众的启动一个http服务器的话JDK6的新特性之一http轻量级服务就派上用场了。
RMI代码动态下载其实就用到了HTTP协议的下载功能,所以,可以把这部分工作给一个简单的通用类就行了。如下:

  1. //RmiHttpServer.java   
  2. import java.io.File;   
  3. import java.io.FileInputStream;   
  4. import java.io.IOException;   
  5. import java.io.OutputStream;   
  6. import java.net.InetSocketAddress;   
  7. import java.util.concurrent.Executors;  
  8. import com.sun.net.httpserver.HttpExchange;   
  9. import com.sun.net.httpserver.HttpHandler;   
  10. import com.sun.net.httpserver.HttpServer;   
  11. import com.sun.net.httpserver.spi.HttpServerProvider;  
  12. public class RmiHttpServer {   
  13. private int port;   
  14. private HttpServer httpServer;   
  15. private boolean isRunning = false;   
  16. private String folder = "";   
  17. private String context = "/";  
  18. public RmiHttpServer() {   
  19.   this.port = 80;   
  20. }  
  21. public RmiHttpServer(int port) {   
  22.   this.port = port;   
  23. }  
  24. public RmiHttpServer(String context, String folder, int port) {   
  25.   this.folder = folder;   
  26.   this.context = context;   
  27.   this.port = port;   
  28. }  
  29. public synchronized void service() {   
  30.   if (isRunning) {   
  31.    System.out.println("HTTP服务已启动...");   
  32.   } else {   
  33.    HttpServerProvider httpServerProvider = HttpServerProvider   
  34.      .provider();   
  35.    InetSocketAddress addr = new InetSocketAddress(port);   
  36.    try {   
  37.     httpServer = httpServerProvider.createHttpServer(addr, 1);   
  38.     httpServer.createContext(context, new RmiHttpHandler(folder));   
  39.     httpServer.setExecutor(Executors.newCachedThreadPool());   
  40.     httpServer.start();   
  41.     System.out.println("HTTP服务已启动...");   
  42.     isRunning = true;   
  43.    } catch (IOException e) {   
  44.     e.printStackTrace();   
  45.     System.out.println("HTTP服务启动失败");   
  46.    }   
  47.   }   
  48. }  
  49. public synchronized void close() {   
  50.   isRunning = false;   
  51.   httpServer.stop(0);   
  52.   System.out.println("HTTP服务关闭");   
  53. }   
  54. }  
  55. class RmiHttpHandler implements HttpHandler {   
  56. public static int BUFFER_LENGTH = 1024;   
  57. private String folder;  
  58. public RmiHttpHandler(String folder) {   
  59.   this.folder = folder;   
  60. }  
  61. public void handle(HttpExchange httpExchange) throws IOException {   
  62.   String response;   
  63.   OutputStream out = httpExchange.getResponseBody();   
  64.   String path;   
  65.   if (folder.equals(""))   
  66.    path = httpExchange.getRequestURI().getPath().substring(1);   
  67.   else   
  68.    path = folder + httpExchange.getRequestURI().getPath();   
  69.   //System.out.println(path);   
  70.   File file = new File(path);   
  71.   if (file.exists() && file.canRead()) {   
  72.    httpExchange.sendResponseHeaders(200, file.length());   
  73.    FileInputStream in = new FileInputStream(file);   
  74.    byte[] buffer = new byte[BUFFER_LENGTH];   
  75.    int count = 0;   
  76.    for (int i = 0; count != -1; i++) {   
  77.     count = in.read(buffer);   
  78.     if (count != -1)   
  79.      out.write(buffer, 0, count);   
  80.    }   
  81.    in.close();   
  82.   } else {   
  83.    response = "error";   
  84.    httpExchange.sendResponseHeaders(404, response.length());   
  85.    out.write(response.getBytes());   
  86.   }   
  87.   out.close();   
  88. }   
  89. }   


下面是个简单的例子来说明代码的动态下载和上面类的使用方法。
服务端建立文件夹server。
首先是要动态下载的接口

  1. //User.java   
  2. import java.io.Serializable;   
  3. public interface User extends Serializable{   
  4. public String getName();   
  5. }  


然后是服务接口

  1. //Hello.java   
  2. import java.rmi.Remote;   
  3. import java.rmi.RemoteException;   
  4. public interface Hello extends Remote {   
  5. public User hello(String name) throws RemoteException;   
  6. }   

两个接口的实现

  1. //UserImpl.java   
  2. public class UserImpl implements User {   
  3. private static final long serialVersionUID = -5401902546629154615L;  
  4. private String name;   
  5.   
  6. public UserImpl(String n){   
  7.   name = n;   
  8. }   
  9. public String getName() {   
  10.   return name+" 你好!我是aa";   
  11. }   
  12. }  
  13.   
  14. //HelloImpl.java   
  15. import java.rmi.RemoteException;   
  16. import java.rmi.server.UnicastRemoteObject;  
  17.   
  18. public class HelloImpl extends UnicastRemoteObject implements Hello {   
  19. private static final long serialVersionUID = -4981732846986548202L;  
  20. public HelloImpl() throws RemoteException {   
  21.   super();   
  22. }  
  23. public User hello(String name) throws RemoteException {   
  24.   System.out.println("服务端负责接口的实例化");   
  25.   return new UserImpl(name);   
  26. }   
  27. }  


启动服务

[c-sharp] view plain copy print ?
  1. //Server.java   
  2. import java.rmi.Naming;   
  3. import java.rmi.RMISecurityManager;   
  4. import java.rmi.RemoteException;   
  5. import java.rmi.registry.LocateRegistry;  
  6. public class Server {   
  7. public static void main(String argv[]) throws RemoteException {   
  8.   System.setProperty("java.security.policy""rmi.policy");//加载安全配置   
  9.   LocateRegistry.createRegistry(1099);   
  10.   if (System.getSecurityManager() == null) {   
  11.    System.setSecurityManager(new RMISecurityManager());   
  12.   }   
  13.   try {   
  14.    Hello hello = new HelloImpl();   
  15.    Naming.rebind("//127.0.0.1/HelloServer", hello);//绑定   
  16.   } catch (Exception e) {   
  17.    e.printStackTrace();   
  18.   }   
  19.   new RmiHttpServer(80).service();//启动http服务,负责将当前目录的字节码动态传输给客户端   
  20. }   
  21. }  

配置文件rmi.policy
grant  {
permission java.security.AllPermission;
};


下面就是客户端,建立文件夹client
我们只需要把客户端需要的接口User.class,Hello.class拷贝到classpath(即client目录下)就可以了,由于用到了代码动态下载,所以得获得许可,我们还需把rmi.policy也拷贝到环境中。

  1. //Client.java   
  2. import java.rmi.*;   
  3. public class Client {   
  4. public static void main(String[] args) throws Exception {   
  5.   System.setProperty("java.security.policy""rmi.policy");   
  6.   System.setProperty("java.rmi.server.codebase","http://127.0.0.1/");//指名需要下载的类的路径  
  7.   //注意,如果没有上面一行,会出现java.lang.ClassNotFoundException: UserImpl错误   
  8.   //因为UserImpl并不在本地环境中   
  9.   System.setSecurityManager(new RMISecurityManager());   
  10.   Hello hello = (Hello) Naming.lookup("//127.0.0.1/HelloServer");    //通过RMI名称查找远程对象   
  11.   System.out.println(hello.hello("syc").getName());//执行下载下来的类的getName方法   
  12. }   
  13. }  

 

接下来执行Server,和Client,就可以看到结果了,如果我们改变UserImpl的实现,再次执行Client时,就会发现变化。

 

JDK5新版RMI编程指南 --转载:http://blog.csdn.net/hqx2008/article/details/1725670

 

 

前言
我前一段时间需要为我的一个Java程序提供远程访问接口,供其他Java程序使用。Java程序可以使用很多种远程访问技术实现这一需求。由于我的远程客户端是java程序,因此,我决定使用RMI这种远程访问技术。RMI是java平台上最快的远程访问技术。
Spring框架为包括RMI在内的各种远程访问技术提供了很好的工具类,能够使我们方便的公布RMI接口和访问RMI远程对象。
但是,我的那个Java程序并没有使用Spring框架。
因此,我研究了怎样在一般的java程序中使用RMI技术。
我寻找了一些RMI资料。根据那些RMI资料,构建一个RMI服务器需要写大量的代码。这太离谱了!一个小小的需求,竟然需要这么大的精力。
后来,我看了javadoc,才发现网上的那些RMI资料都已经过时了。JDK5中,RMI技术已经得到了重大更新。现在使用JDK提供的RMI类,可以相当简单的发布RMI服务!
 
正文
RMI简介
    RMI(Remote Method Invocation),远程方法调用,是一种远程调用java方法的技术。是最早开发出来的java远程访问技术,也是EJB等远程访问技术的基础。
过 去,使用RMI技术共有6个步骤要走: (1)定义和实现远端接口中的参数 (2) 定义和实现远端接口 (3) 编写服务端代码 (4)编写客户端代码 (5)生成stub和skeltion ,并将stub打包到客户端jar中,将skeltion打包到服务端jar中 (6)启动rmiregistry , 并将服务注册到rmiregistry中,然后运行代码。
这还需要执行一些命令行程序,非常繁杂!这与RMI这样一种简单的技术非常不相称!
好在JDK5中,RMI得到了重大更新。RMI已经成为一种真正轻量级的简单技术!
JDK5中,使用了动态代理技术,实现了动态生成stub和skeltion类,从而省却了相当多的繁琐工作。RMI的创建和发布已经变得非常简单!
 
建立RMI服务器和注册表
1,Registry接口
java.rmi.registry
接口 Registry
所有超级接口:

public interface Registry
extends Remote
Registry 是简单远程对象注册表的一个远程接口,它提供存储和获取绑定了任意字符串名称的远程对象引用的方法。 bindunbindrebind 方法用于改变注册表中的名称绑定, lookuplist 方法用于查询当前的名称绑定。
 
Registry接口,是RMI对象的注册表,客户端通过这个注册表查询和得到RMI对象。
 
2,LocateRegistry类
java.rmi.registry
类 LocateRegistry
java.lang.Object
                              java.rmi.registry.LocateRegistry

public final class LocateRegistry
extends Object
LocateRegistry 用于获得对特定主机(包括本地主机)上引导远程对象注册表的引用,或用于创建一个接受对特定端口调用的远程对象注册表。
 
   

static Registry
createRegistry (int port)
          
创建并导出接受指定 port 请求的本地主机上的 Registry 实例。

   
LocateRegistry类的createRegistry()方法,创建并得到对远程RMI对象注册表的引用。
 
    现在,我们就成功创建了一个RMI服务器和对象注册表。
然后,通过调用Registry接口的方法:

void
rebind (String name,Remote obj)
          
用提供的远程引用替换此注册表中指定的 name 绑定。

我们能够把RMI对象发布到RMI服务器上,并存放到注册表中,能够被RMI客户端发现并访问。
至此,RMI服务器就建立好了!
 
 
编写RMI对象
    Registry接口的方法:

void
rebind (String name,Remote obj)
          
用提供的远程引用替换此注册表中指定的 name 绑定。

这表明,RMI对象必须是实现了Remote接口的对象。实际上,任何java上的可远程访问对象都需要实现Remote接口!
但是,仅仅实现了Remote接口的对象还不能作为RMI对象被发布。
 
UnicastRemoteObject类
java.rmi.server
类 UnicastRemoteObject
java.lang.Object
 java.rmi.server.RemoteObject
      java.rmi.server.RemoteServer
          java.rmi.server.UnicastRemoteObject
所有已实现的接口:
直接已知子类:

public class UnicastRemoteObject
extends RemoteServer
用于导出带 JRMP 的远程对象和获得与该远程对象通信的 stub。
对于下面的构造方法和静态 exportObject 方法,正在导出的远程对象的 stub 按以下方式获得:
o                                           最后,用 RemoteRef构造 stub 类的实例。
 
 
使用UnicastRemoteObject类的方法:

static Remote
exportObject (Remote obj, int port)
          
使用提供的特定端口导出远程对象,以便能够接收传入的调用。

可以把实现了Remote接口的类生成为RMI对象。可以发布到RMI注册表上!
 
 
生成RMI类的存根类
使用UnicastRemoteObject类的方法:

static Remote
exportObject (Remote obj, int port)
          
使用提供的特定端口导出远程对象,以便能够接收传入的调用。

使用这个方法返回的Remote对象,可以被发布到RMI的注册表上。使用动态代理,它能够自动生成stub类。这样,只要使用UnicastRemoteObject.exportObject (Remote obj, int port) 方法创建RMI注册表上的RMI对象,就不需要我们再关心stub和 skeltion 类的生成问题。
注意,

static RemoteStub
exportObject (Remote obj)
          
使用匿名端口导出远程对象,以便能够接收传入的调用。

    这个方法创建的RMI对象,客户端不能自动生成stub类!请不要使用这个方法。
 
    请注意,

static Remote
exportObject (Remote obj, int port)
          
使用提供的特定端口导出远程对象,以便能够接收传入的调用。

我们使用这个方法时,应该使用0作为第二个参数的值。否则可能会出错!
 
 
RMI最佳实践
看了上面的文字,是不是觉得还是有些复杂呢?接下来,我们看看实际怎样编写RMI程序,你将发现RMI编程确实是非常非常简单!
 
实现Remote接口
RMI发布的类,必须实现Remote接口。我们可以让我们的RMI类实现的接口extend Remote接口。
这就是Remote接口。
public interface Remote {}
可 以看到,它实际上是空的,什么都没有。它仅仅是一个标记,表示Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或间接实现此接口。只有在“远程接口”(扩展 java.rmi.Remote 的接口)中指定的这些方法才可远程使用。
这种标记接口,现在实际上可以用“标注”来达到同样的目的。当初标注没有引入Java中,因此就有了这样的空接口!
 
另外,所有能够远程访问的方法,都必须抛出RemoteException。这是因为,远程调用可能会碰到一些如网络中断这样的错误。
 
可发布为RMI的类
可发布为RMI的类,它必须有参数为空的构造器,构造器必须声明为可抛出RemoteException。
建议构造其中这样写:
UnicastRemoteObject.exportObject(this, 0);
也就是说,构造器构造的是一个可以动态生成存根的RMI对象。
 
当然,你也可以不这么写,但是那样的话就要在注册RMI对象时多写一些代码了!
 
另外,所有远程调用方法的参数,必须实现Serializable接口。
 
发布RMI对象
下面是我的发布RMI对象的代码:
//这是RMI服务器使用的端口,我使用了配置文件
String rMIPort = Config.getInstance().get("RMIPort");
//这样就在指定的端口创建并启动了RMI服务器。否则,你需要在命令行中输入:
//start rmiregistry
//手工启动RMI服务器
              Registry registry = LocateRegistry.createRegistry(new Integer(rMIPort)
                            .intValue());
//我在配置文件中配置了一些RMI类,每一个包括注册表的名字和RMI类的全名
                     Map<String, String> rmis = Config.getInstance().getRmis();
              Set<Map.Entry<String, String>> entrys = rmis.entrySet();
//用叠代器全部拿到,然后一个个放到RMI注册表中,公布出来!
              Iterator it = entrys.iterator();
              while (it.hasNext()) {
                     Map.Entry<String, String> tmp = (Map.Entry<String, String>) it
                                   .next();
                     try {
//创建RMI对象,可以动态生成存根
                            Remote stub = (Remote) Class.forName(tmp.getValue())
                                          .newInstance();
//以指定的名字发布RMI对象到注册表
                            registry.rebind(tmp.getKey(), stub);
                     } catch (InstantiationException e) {
                            /*
                             */
 
                            e.printStackTrace();
                     } catch (IllegalAccessException e) {
                            /*
                             */
 
                            e.printStackTrace();
                     } catch (ClassNotFoundException e) {
                            /*
                             */
 
                            e.printStackTrace();
                     } catch (RemoteException e) {
                            /*
                             */
 
                            e.printStackTrace();
                     }
 
              }
 
访问RMI对象的客户端
    访问RMI对象的客户端也是非常非常简单的。
如:
//RMI服务器的IP地址
String rMIHost=Config.getInstance().get("RMIHost");
//RMI服务器的端口
String rMIPort=Config.getInstance().get( "RMIPort" );
// 得到RMI服务器注册表的引用
            Registry registry = LocateRegistry.getRegistry(rMIHost , new Integer(rMIPort).intValue());
// 查找并得到RMI对象的实例。
            IServerREQTerminalConfigService stub = (IServerREQTerminalConfigService) registry.lookup( "ServerREQTerminalConfigService" );
         // 调用远程RMI服务器上的对象的方法!
            stub.rmiRemote();
         
 
就这么简单!
 
结语
在JDK5发布之前,使用RMI是一件非常繁琐的事情。甚至很多程序员把EJB当作对RMI的封装,当作RMI的简化版!
而今,RMI已经得到了巨大的改进。作为Java平台上性能最好远程访问技术,如今也是最简单的远程访问技术,RMI理应得到更广泛的应用。

 

 

 

RMI如何固定分配端口

转自:http://blog.csdn.net/ygzx2008/article/details/2593793

RMI之所以使用的范围受限制主要有两方面原因,其一:必须要是java,平台的异构性受到限

制;其二:穿越防火墙不方便。这里主要谈谈RMI如何通过固定分配端口来穿越防火墙。    

RMI穿越防火墙不方便主要是因为除了RMI服务注册的端口(默认1099)外,与RMI的通讯还需

要另外的端口来传送数据,而另外的端口是随机分配的,所以要想RMI的客户能通过防火墙来

与RMI服务通讯,则需要能让随机分配的端口固定下来,具体做法如下:

1、增加一个文件SMRMISocket.java
import   java.rmi.server.*; 
import   java.io.*;
import   java.net.*; 

public class SMRMISocket   extends   RMISocketFactory  

    public Socket   createSocket(String   host,   int   port)  throws   IOException
    { 
         return   new   Socket(host,port); 

     } 

     public   ServerSocket   createServerSocket(int   port)   throws   IOException  
    { 
         if   (port   ==   0)
         {
                  port   =   10990; //不指定就随机分配了
                  return   new   ServerSocket(port); 
         }
     } 

}  

 2、在实例化UnicastRemoteObject的子类前(即在得到Registry对象和邦定服务之前)加入一下代码:
   try
   {
        RMISocketFactory.setSocketFactory(new SMRMISocket());

    } catch (Exception ex) {

    }

这样的话RMI分配的端口就被固定了,防火墙只需要打开1099和10990端口即可。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值