轉載:
Java RMI 指的是遠程方法調用 (Remote Method Invocation)。它是一種機制,可以讓在某個 Java 虛擬機上的對象調用另外一個 Java 虛擬機中的對象上的方法。能夠用此方法調用的任何對象必須實現該遠程接口。
Java RMI不是什麼新技術(在Java1.1的時代都有了),但倒是是很是重要的底層技術。
大名鼎鼎的EJB都是創建在rmi基礎之上的,如今還有一些開源的遠程調用組件,其底層技術也是rmi。
在大力鼓吹Web Service、SOA的時代,是否是每一個應用都應該選用笨拙的Web Service組件來實現,經過對比測試後,RMI是最簡單的,在一些小的應用中是最合適的。
下面經過一個簡單的例子來講明RMI的原理和應用,下面這個例子是一個簡單HelloWorld,但已涵蓋RMI的核心應用與開發模式。
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2008-8-7 21:50:02
* 定義一個遠程接口,必須繼承Remote接口,其中須要遠程調用的方法必須拋出RemoteException異常
*/
public
interface IHello
extends Remote {
/**
* 簡單的返回「Hello World!"字樣
* @return 返回「Hello World!"字樣
* @throws java.rmi.RemoteException
*/
public String helloWorld()
throws RemoteException;
/**
* 一個簡單的業務方法,根據傳入的人名返回相應的問候語
* @param someBodyName 人名
* @return 返回相應的問候語
* @throws java.rmi.RemoteException
*/
public String sayHelloToSomeBody(String someBodyName)
throws RemoteException;
}
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2008-8-7 21:56:47
* 遠程的接口的實現
*/
public
class HelloImpl
extends UnicastRemoteObject
implements IHello {
/**
* 由於UnicastRemoteObject的構造方法拋出了RemoteException異常,所以這裏默認的構造方法必須寫,必須聲明拋出RemoteException異常
*
* @throws RemoteException
*/
public HelloImpl()
throws RemoteException {
}
/**
* 簡單的返回「Hello World!"字樣
*
* @return 返回「Hello World!"字樣
* @throws java.rmi.RemoteException
*/
public String helloWorld()
throws RemoteException {
return
"Hello World!";
}
/**
* 一個簡單的業務方法,根據傳入的人名返回相應的問候語
*
* @param someBodyName 人名
* @return 返回相應的問候語
* @throws java.rmi.RemoteException
*/
public String sayHelloToSomeBody(String someBodyName)
throws RemoteException {
return
"你好," + someBodyName +
"!";
}
}
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2008-8-7 22:03:35
* 建立RMI註冊表,啓動RMI服務,並將遠程對象註冊到RMI註冊表中。
*/
public
class HelloServer {
public
static
void main(String args[]) {
try {
//建立一個遠程對象
IHello rhello =
new HelloImpl();
//本地主機上的遠程對象註冊表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(">>>>>INFO:遠程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();
}
}
}
/**
* Created by IntelliJ IDEA.
* User: leizhimin
* Date: 2008-8-7 22:21:07
* 客戶端測試,在客戶端調用遠程對象上的遠程方法,並返回結果。
*/
public
class HelloClient {
public
static
void main(String args[]){
try {
//在RMI服務註冊表中查找名稱爲RHello的對象,並調用其上的方法
IHello rhello =(IHello) Naming.lookup(
"rmi://localhost:8888/RHello");
System.out.println(rhello.helloWorld());
System.out.println(rhello.sayHelloToSomeBody("熔岩"));
} catch (NotBoundException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
運行RMI服務端程序:
運行RMI客戶端程序:
總結:
從上面的過程來看,RMI對服務器的IP地址和端口依賴很緊密,可是在開發的時候不知道未來的服務器IP和端口如何,可是客戶端程序依賴這個IP和端口。
這也是RMI的侷限性之一。這個問題有兩種解決途徑:一是經過DNS來解決,二是經過封裝將IP暴露到程序代碼以外。
RMI的侷限性之二是RMI是Java語言的遠程調用,兩端的程序語言必須是Java實現,對於不一樣語言間的通信能夠考慮用Web Service或者公用對象請求代理體系(CORBA)來實現。
-----------------------------------------------------------------------------------------------------------------------------------------------------
RMI:遠程方法調用(Remote Method Invocation)。可以讓在某個Java虛擬機上的對象像調用本地對象同樣調用另外一個java 虛擬機中的對象上的方法。html
RMI遠程調用步驟:java
1,客戶對象調用客戶端輔助對象上的方法linux
2,客戶端輔助對象打包調用信息(變量,方法名),經過網絡發送給服務端輔助對象bash
3,服務端輔助對象將客戶端輔助對象發送來的信息解包,找出真正被調用的方法以及該方法所在對象服務器
4,調用真正服務對象上的真正方法,並將結果返回給服務端輔助對象網絡
5,服務端輔助對象將結果打包,發送給客戶端輔助對象ide
6,客戶端輔助對象將返回值解包,返回給客戶對象函數
7,客戶對象得到返回值工具
對於客戶對象來講,步驟2-6是徹底透明的測試
搭建一個RMI服務的過程分爲如下7步;
1,建立遠程方法接口,該接口必須繼承自Remote接口
Remote 接口是一個標識接口,用於標識所包含的方法能夠從非本地虛擬機上調用的接口,Remote接口自己不包含任何方法
- package server;
-
- import java.rmi.Remote;
- import java.rmi.RemoteException;
-
- public interface Hello extends Remote {
- public String sayHello(String name) throws RemoteException;
- }
因爲遠程方法調用的本質依然是網絡通訊,只不過隱藏了底層實現,網絡通訊是常常會出現異常的,因此接口的全部方法都必須拋出RemoteException以說明該方法是有風險的
2,建立遠程方法接口實現類:
UnicastRemoteObject類的構造函數拋出了RemoteException,故其繼承類不能使用默認構造函數,繼承類的構造函數必須也拋出RemoteException
因爲方法參數與返回值最終都將在網絡上傳輸,故必須是可序列化的
- package server;
-
- import java.rmi.RemoteException;
- import java.rmi.server.UnicastRemoteObject;
-
- public class HelloImpl extends UnicastRemoteObject implements Hello {
- private static final long serialVersionUID = -271947229644133464L;
-
- public HelloImpl() throws RemoteException{
- super();
- }
-
- public String sayHello(String name) throws RemoteException {
- return "Hello,"+name;
- }
- }
3,利用java自帶rmic工具生成sutb存根類(jdk1.5.0_15/bin/rmic)
jdk1.2之後的RMI能夠經過反射API能夠直接將請求發送給真實類,因此不須要skeleton類了
sutb存根爲遠程方法類在本地的代理,是在服務端代碼的基礎上生成的,須要HelloImpl.class文件,因爲HelloImpl繼承了Hello接口,故Hello.class文件也是不可少的
Test
- - server
- - - - Hello.class
- - - - HelloImpl.class
方式一:
- [name@name Test]$ cd /home/name/Test/
- [name@name Test]$ rmic server.HelloImpl
方式二:
- [name@name Test]$ rmic -classpath /home/name/Test server.HelloImpl
運行成功後將會生成HelloImpl_Stub.class文件
4,啓動RMI註冊服務(jdk1.5.0_15/bin/rmiregistry)
方式一:後臺啓動rmiregistry服務
- [name@name jdk]$ jdk1.5.0_15/bin/rmiregistry 12312 &
- [1] 22720
- [name@name jdk]$ ps -ef|grep rmiregistry
- name 22720 13763 0 16:43 pts/3 00:00:00 jdk1.5.0_15/bin/rmiregistry 12312
- name 22737 13763 0 16:43 pts/3 00:00:00 grep rmiregistry
若是不帶具體端口號,則默認爲1099
方式二:人工建立rmiregistry服務,須要在代碼中添加:
- LocateRegistry.createRegistry(12312);
5,編寫服務端代碼
- package server;
-
- import java.rmi.Naming;
- import java.rmi.registry.LocateRegistry;
-
- public class HelloServer {
- public static void main(String[] args) {
- try{
- Hello h = new HelloImpl();
-
-
-
-
-
-
- Naming.bind("rmi://192.168.58.164:12312/Hello", h);
- System.out.println("HelloServer啓動成功");
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- }
先建立註冊表,而後才能在註冊表中存儲遠程對象信息
6,運行服務端(58.164):
Test
- - server
- - - - Hello.class
- - - - HelloImpl.class
- - - - HelloServer.class
- [name@name ~]$ java server.HelloServer
- HelloServer啓動成功
固然/home/name/Test必定要在系統CLASSPATH中,不然會報找不到相應的.class文件
7,編寫客戶端代碼
- package client;
-
- import java.net.MalformedURLException;
- import java.rmi.Naming;
- import java.rmi.NotBoundException;
- import java.rmi.RemoteException;
-
- import server.Hello;
-
- public class HelloClient {
- public static void main(String[] args) {
- try {
- Hello h = (Hello)Naming.lookup("rmi://192.168.58.164:12312/Hello");
- System.out.println(h.sayHello("zx"));
- } catch (MalformedURLException e) {
- System.out.println("url格式異常");
- } catch (RemoteException e) {
- System.out.println("建立對象異常");
- e.printStackTrace();
- } catch (NotBoundException e) {
- System.out.println("對象未綁定");
- }
- }
- }
8,運行客戶端(58.163):
Test
- - client
- - - - HelloClient.class
- - server
- - - - Hello.class
- - - - HelloImpl_Stub.class//服務端生成的存根文件
- [name@name client]$ java client.HelloClient
- Hello,zx
同服務器端,/home/name/Test必定要在系統CLASSPATH中
PS:
1,客戶端所在服務和服務端所在的服務器網絡必定要通(一開始浪費了不少時間,最後才發現是網絡不通)
2,全部代碼在jdk1.5.0_15,Linux服務器上調試經過
3,若是java命令運行提示找不到類文件,則爲CLASSPATH配置問題
- [name@name ~]$ vi .bash_profile
- JAVA_HOME=/home/name/jdk/jdk1.5.0_15
- export JAVA_HOME
- PATH=$JAVA_HOME/bin:$PATH
- export PATH
- CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:/home/name/Test
- export CLASSPATH
JAVA_HOME爲jdk的根目錄
PATH爲java工具類路徑(java,javac,rmic等)
CLASSPATH爲java .class文件的存放路徑,使用java命令運行.class文件時即會在該參數配置的路徑下尋找相應文件
Java RMI的缺點:
1,從代碼中也能夠看到,代碼依賴於ip與端口
2,RMI依賴於Java遠程消息交換協議JRMP(java Remote Messaging Protocol),該協議爲java定製,要求服務端與客戶端都爲java編寫
還有
http://blog.csdn.net/a19881029/article/details/9465663
http://blog.sina.com.cn/s/blog_5e971b850100rtti.html