一、概念
当客户代码要在远程对象上调用一个远程方法时,实际上调用的是代理对象上的一个普通方法,我们称此代理对象为存根。存根在客户端上,而不是在服务器上。存根构造了一个信息块:远程对象的标识符、方法的描述、编组后的参数。
参数编组:把远程方法所需的参数打包成一组字节。目的是将参数转换成合适在虚拟机之间进行传递的格式。在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));
}
}