一、概念
當客戶代碼要在遠程對象上調用一個遠程方法時,實際上調用的是代理對象上的一個普通方法,我們稱此代理對象爲存根。存根在客戶端上,而不是在服務器上。存根構造了一個信息塊:遠程對象的標識符、方法的描述、編組後的參數。
參數編組:把遠程方法所需的參數打包成一組字節。目的是將參數轉換成合適在虛擬機之間進行傳遞的格式。在RMI中,對象是使用序列化機制進行編碼的。
客戶端存根對來自服務端的返回值或者異常進行反編組,就成了調用存根的返回值。
二、RMI
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; }
注意:1)遠程對象必須實現Remote接口。
2)接口中的所有方法必須拋出RemoteException。
2.創建遠程方法實現類
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; } }
注意:1)UnicastRemoteObject類的構造函數拋出了RemoteException,這個構造器使得它的對象可供遠程訪問,因爲其超類構造器也會拋出這個異常,故其繼承類不能使用默認構造函數,繼承類的構造函數必須也拋出RemoteException。
2)由於方法參數與返回值最終都將在網絡上傳輸,故必須是可序列化的
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
方式一: view plain copy
[name@name Test]$ cd /home/name/Test/
[name@name Test]$ rmic server.HelloImpl
方式二:view plain copy
[name@name Test]$ rmic -classpath /home/name/Test server.HelloImpl
運行成功後將會生成HelloImpl_Stub.class文件
4.啓動RMI註冊服務(jdk1.5.0_15/bin/rmiregistry)
方式一:後臺啓動rmiregistry服務view plain copy
[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
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(); //創建並導出接受指定port請求的本地主機上的Registry實例。 //LocateRegistry.createRegistry(12312); /** Naming 類提供在對象註冊表中存儲和獲得遠程對遠程對象引用的方法 * Naming 類的每個方法都可將某個名稱作爲其一個參數, * 該名稱是使用以下形式的 URL 格式(沒有 scheme 組件)的 java.lang.String: * //host:port/name * host:註冊表所在的主機(遠程或本地),省略則默認爲本地主機 * port:是註冊表接受調用的端口號,省略則默認爲1099,RMI註冊表registry使用的著名端口 * name:是未經註冊表解釋的簡單字符串 */ //Naming.bind("//host:port/name", h); 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 view plain copy
[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//服務端生成的存根文件 copy
[name@name client]$ java client.HelloClient
Hello,zx
同服務器端,/home/name/Test一定要在系統CLASSPATH中
spring中的RMI
Spring RMI中,主要有兩個類:org.springframework.remoting.rmi.RmiServiceExporter和org.springframework.remoting.rmi.RmiProxyFactoryBean
服務端使用RmiServiceExporter暴露RMI遠程方法,客戶端用RmiProxyFactoryBean間接調用遠程方法。
首先,也是兩個工程,服務端用Web工程,因爲使用Spring,我們依託Web容器來完成。
1、在該服務端Web工程中添加接口,普通接口,無需繼承其他view plain copy
package rmi.test;
public interface HelloRMIService {
public int getAdd(int a, int b);
}
2、接口的實現類view plain copy
package rmi.test;
public class HelloRMIServiceImpl implements HelloRMIService {
@Override
public int getAdd(int a, int b) {
return a+b;
}
}
3、在該服務端Web工程中添加Spring的bean配置文件,比如命名爲rmiServer.xml,內容如下: view plain copy
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<bean id="helloRMIServiceImpl" class="rmi.test.HelloRMIServiceImpl"> </bean>
<!-- 將一個類發佈爲一個RMI服務 -->
<bean id="myRMIServer" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="helloRMI"></property>
<property name="service" ref="helloRMIServiceImpl"></property>
<property name="serviceInterface" value="rmi.test.HelloRMIService"></property>
<property name="registryPort" value="9999"></property>
</bean>
</beans>
說明:這裏不詳細的說明了,主要配置了真實實現類,用RmiServiceExporter暴露時,配置property要注意的有service,serviceName,serviceInterface,端口registryPort。
啓動Web工程的服務器,該配置文件應該被Spring的監聽器監聽,並加載,啓動成功後,服務端就算建好了。如果服務器是在localhost啓動的,那麼暴露的RMI的IP也是localhost,如果需要使用其他IP,需要讓服務器在其他的IP啓動。
這裏沒有web服務器,使用main模擬啓動,如下:
[html] view plain copy
package rmi.test;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class RMIServiceTest {
public static void main(String[] args) {
new ClassPathXmlApplicationContext("rmiServer.xml");
}
}
客戶端調用:爲了方便也只新建一個簡單的Java Project,使用靜態的java代碼來調用了。
1、 在源文件src下建立一個rmiClient.xml view plain copy
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">
<!-- 客戶端 -->
<bean id="myRMIClient" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceInterface" value="rmi.test.HelloRMIService"></property>
<property name="serviceUrl" value="rmi://127.0.0.1:9999/helloRMI"></property>
</bean>
</beans>
這裏注意到RmiProxyFactoryBean的兩個重要的property:serviceUrl和serviceInterface,HelloRMIService接口可以從服務端的接口打成jar包來提供。
2、 新建java類 view plain copy
package rmi.test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class RMIClient {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplica tionContext("rmi/test/rmiClient.xml");
HelloRMIService helloRMIService = applicationContext.getBean(" myRMIClient",HelloRMIService.class);
System.out.println(helloRMIService.getAdd(3, 4));
}
}