RMI调用的过程,我的理解大致如下:
- 客户端和服务器端约定好要用于远程调用的方法的接口(包括方法名和输入输出参数)。
- 服务器端将远程服务绑定到某个端口,进行TCP/IP监听(例如监听地址为rmi://127.0.0.1:1099)。
- 客户端与服务器建立TCP/IP连接(当然事先要知道服务器端的监听地址),按照接口声明的方法发送数据(可序列化的Java对象)以及要调用的服务名、方法名等。
- 服务器端将接收到的数据(字节流)反序列化,得到要调用的方法和参数信息;然后服务器端进行本地计算(方法调用)。
- 服务器端将计算的结果(可序列化的Java对象)发送给客户端。
- 客户端从接收到的返回数据(字节流)中进行反序列化,得到运算结果。
- 客户端断开与服务器端的TCP/IP连接,远程调用结束。
2 Spring中RMI的使用
开始下面的步骤之前,我们要从Spring的网站下载开发所需要的jar包(实现RMI的部分的包在org.springframework.remoting.rmi中)。
2.1 服务器端开发- 声明要发布为RMI服务的接口
package demo.rmi.server;
/**
* The purpose of this class is to define all presence method
*
*/
public interface IPresence {
/**
* subscribe presence info for the watcher
*
* @param watcherUri
* @param presentities
*/
public void subscribePresence(String watcherUri, String[] presentities);
}
Ø 实现要发布为RMI服务的接口
package demo.rmi.server;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The purpose of this class is to implment presence operation
*/
public class PresenceImpl implements IPresence {
private static Logger log = LoggerFactory.getLogger(PresenceImpl.class);
public void subscribePresence(String watcherUri, String[] presentities) {
if ((watcherUri == null) || (presentities == null)) {
log.warn("watcherUri or presentities is null!");
return;
}
if (log.isInfoEnabled()) {
StringBuilder s = new StringBuilder();
if (presentities != null) {
for (String str : presentities) {
s.append(str).append(" ");
}
}
log.info("subscribe presence for watcher:{}, presentities:{}",
watcherUri, s);
}
}
}
- 发布自己的RMI服务
首先在Spring配置文件中(这里我命名为rmi-server.xml),配置所需要的Bean。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www./dtd/spring-beans-2.0.dtd">
<beans>
<bean id="presenceService" class="demo.rmi.server.PresenceImpl" />
<!-- define a RMI service listening at port 1001 -->
<bean id="serviceExporter_1001" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service">
<ref bean="presenceService" />
</property>
<property name="serviceName">
<value>presenceService</value>
</property>
<property name="serviceInterface">
<value>demo.rmi.server.IPresence</value>
</property>
<!-- defaults to 1099 -->
<property name="registryPort" value="1001" />
</bean>
</beans>
然后启动我们的RMI服务,进行绑定和监听。
package demo.rmi.server;
import java.rmi.RemoteException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.remoting.rmi.RmiServiceExporter;
/**
* The purpose of this class is to represent a rmi server via spring remoting
* support
*
*/
public class RMIServer {
private static Logger log = LoggerFactory.getLogger(RMIServer.class);
/**
* start server by hard code
*/
public void startByAPI() {
RmiServiceExporter exporter = new RmiServiceExporter();
try {
exporter.setServiceInterface(IPresence.class);
exporter.setServiceName("serviceByAPI");
exporter.setService(new PresenceImpl());
exporter.setRegistryPort(1001);
exporter.afterPropertiesSet();
} catch (RemoteException e) {
log.error("error when export service", e);
}
}
/**
* start server by configuration in bean.xml
*/
public void startByCfg() {
ApplicationContext context = new ClassPathXmlApplicationContext(
"rmi-server.xml");
if (log.isInfoEnabled()) {
log.info("application context 's display name :{}", context
.getDisplayName());
log.info("RMI server initialized successfully!");
}
}
/**
* main method for start a rmi server
*
* @param args
*/
public static void main(String[] args) {
// new RMIServer().startByAPI();
new RMIServer().startByCfg();
}
}
当我们初始化Spring的ApplicationContext后,Spring会自动完成RMI服务的绑定和监听。
2.2 客户端开发首先在Spring配置文件中(这里我命名为rmi-client.xml),配置所需要的Bean。
<?xml version="1.0" encoding="UTF-8"?>
<beans>
<!-- define a RMI client service which send to port 1001 -->
<bean id="prensenceServiceProxy_1001" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl">
<value>rmi://127.0.0.1:1001/presenceService</value>
</property>
<property name="serviceInterface">
<value>demo.rmi.server.IPresence</value>
</property>
</bean>
</beans>
然后我们可以编写客户端进行RMI调用的代码。
package demo.rmi.client;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import demo.rmi.server.IPresence;
/**
* The purpose of this class is to represent a RMI client
*/
public class RMIClient {
private static Logger log = LoggerFactory.getLogger(RMIClient.class);
/**
* @param args
*/
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext(
"rmi-client.xml");
if (log.isInfoEnabled()) {
log.info("application context 's display name :{}", context
.getDisplayName());
log.info("RMI client initialized successfully!");
}
// test get a RMI client service via Spring API at runtime
// RmiProxyFactoryBean factory = new RmiProxyFactoryBean();
// factory.setServiceInterface(IPresence.class);
// factory.setServiceUrl("rmi://127.0.0.1:1002/presenceService");
// factory.afterPropertiesSet();
//
// IPresence service2 = (IPresence) factory.getObject();
//
// String[] wt = service2.getMyWatchers("sip:test@mycompany.com");
// System.out.println("service2 works fine,response watchers 's length:"
// + wt.length);
IPresence service = (IPresence) context
.getBean("prensenceServiceProxy_1001");
String watcher = "sip:alice@mycompany.com";
String[] presentities = new String[] { "sip:bob@ mycompany.com",
"sip:susan@mycompany.com" };
StringBuilder presentitiesInfo = new StringBuilder();
for (String str : presentities) {
presentitiesInfo.append(str).append(" ");
}
if (log.isInfoEnabled()) {
log.info("now start send subscribe presnece request for watcher:{}"
+ " ,presentities:{}", watcher, presentitiesInfo);
}
service.subscribePresence(watcher, presentities);
}
}
大家可以注意到,我分别在服务器端和客户端放入了使用Spring API动态(即不在Spring配置文件中固定配置)进行RMI监听和调用的代码,可供需要时参考。