使用RMI
RMI表示远程方法调用。开发和访问RMI服务非常麻烦,但是Spring提供了一个代理工厂bean来简化RMI模型,它允许你把RMI服务注入到你的Spring应用中就好像本地的JavaBean一样。Spring同时还提供了一个远程服务输出者(exporter)。它可以把你的Spring bean转化成RMI服务。
一、 串联RMI服务
如前所述,RoadRantz需要查询第三方服务。我们可以编写一个工厂方法来获取所需服务的引用:
private String citationUrl = “rmi:/citation/CitationService”;
public CitationService lookupCitationService() throw RemoteException,
NotBoundException, MalformedURLException {
CitationService citationService =
(CitationService)Naming.lookup(citationUrl).;
return citationService;
}
citationUrl属性需要被设置成RMI服务的地址。因此,RoadRantz每次需要使用citation服务时,它都需要调用lookupCitationService方法。这就存在着两个问题:
·传统的RMI查询会导致抛出RemoteException, NotBoundException或MalformedURLException异常,而我们必须要捕获或重新抛出这些异常。
·任何请求citation服务的代码都需要调用lookupCitationService来获取一个服务的引用。
RMI查询过程中抛出的异常通常都是致命的或是不可恢复的。比如MalformedURLException异常,它就表明给定的服务地址无效。如果要从该异常中恢复至少需要重新配置还有可能要重新编译。没有任何try/catch语句块能够很好地恢复异常。
另外,lookupCitationService方法更是直接破坏了依赖注入的原则,因为lookupCitationService的客户要确定它在请求一个服务并且需要确定所求服务的位置。理想情况下,你应该把一个CitationService对象注入到需要它的bean中,而不是让那些bean对象自己去查找。依赖注入可以让CitationService的使用者不需要关心CitationService是从哪里来的。
Spring的RmiProxyFactoryBean是一个工厂bean,它会创建一个RMI服务的代理。方法如下:
<?xml version="1.0" encoding="UTF-8"?>
<bean id="citationService"
class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<property name="serviceUrl"
value="rmi://${citationhost}/CitationService" />
<property name="serviceInterface"
value="com.tickettodriver.CitationService" />
</bean>
RMI服务的URL通过serviceUrl属性进行设置。serviceInterface属性指定了服务实现接口。有个被定义为由Spring管理的bean的服务,你就可以把它注入到另一个bean中,就好像你之前注入一个非远程bean一样。比如,假设RantServiceImpl需要使用citation服务来获取车辆的citation列表,使用方法如下:
<bean id="rantService"
class="com.roadrantz.service.RantServiceImpl">
<property name="citationService">
<ref bean="citationService" />
</property>
</bean>
这种方式访问的好处在于RantServiceImpl并不知道它在处理一个RMI服务。它只是通过DI接收到一个CitationService对象,而不关心接收到的对象从哪里来。另外,代理还会捕获服务抛出的任何RemoteException异常,并重新把它们作为运行时异常抛出,这样你就可以放心地忽略它们了。当你要替换远程服务——可能是另一个服务的实现或是一个用来做单元测试的模拟实现的时候这就显得非常有用。
RmiProxyFactoryBean简化了RMI服务的使用,但这只是RMI会话的一方面,下面让我们来看看Spring是如何为RMI的服务端提供支持的。
二、 导出RMI服务
创建RMI服务涉及下面的步骤:
·在服务实现类中编写会抛出java.rmi.RemoteException的方法;
·创建服务接口继承java.rmi.Remote;
·运行RMI编译器(rmic)产生客户stub类和服务器skeleton类;
·注册RMI服务
我们可以发现,发布一个简单的RMI服务需要做这么多工作。更糟的是,RemoteException和MalformedURLException异常会被抛出很多次。这些异常通常都表明哪些不可恢复的致命错误,但是你依然希望编写一段固定代码来捕获和处理这些异常。很明显,没有Spring的帮助,你需要编写很多代码来发布一个RMI服务。
在Spring中创建一个RMI服务
Spring提供了一条简便的方法来发布RMI服务。在Spring中你只需编写一个纯java类就可以执行服务的功能。剩下的工作Spring会为你处理。
要创建citation查询的RMI服务,我们可以这样做:
public interface CitationService {
Citation[] getCitationsForVehicle(String stats, String plateNumber);
}
服务接口没有继承java.rmi.Remote并且getCitationsForVehicle方法也没有抛出RemoteException异常,因而简化了接口的开发。更重要的是,一个客户通过这个接口访问服务将不需要捕获那些它们不需要处理的异常。下面你需要定义服务实现类。例如:
public class CitationServiceImpl implements CitationService {
public CitationServiceImpl() {}
public Citation[] getCitationsForVehicle(String state, String plateNumber) {
Citation[] citations;
… // looks up citations
return citations;
}
}
这次CitationServiceImpl是一个纯java类。我们不必实现java.rmi.Remote,也不必抛出RemoteException异常。实际上,这个类并不知道它会以远程方式使用。除此之外,你还需要配置CitationServiceImpl作为Spring配置文件中的一个bean:
<bean id="citationService"
class="com.tickettodrive.CitationServiceImpl">
...
</bean>
值得注意的是,没有地方告诉我们这个版本的CitationServiceImpl内部就是一个RMI。它只是一个Spring配置文件中声明的简单java类,和其他声明的POJO没有任何区别。实际上,我们完全可以以一种非远程调用的方式来使用它。
但这里,我们关心的是以远程调用的方式来使用它。因此,你还需要将CitationServiceImpl作为一个RMI服务导出。与传统的RMI使用rmic和手工注册不同,我们使用Spring提供的RmiServiceExporter。
RmiServiceExporter可以将任何Spring管理的bean作为RMI服务导出。RmiServiceExporter会将bean封装在一个适配器(adapter)类中。adapter类会被绑定到RMI注册表中并以代理的方式向服务类发送请求,在这里就是CitationServiceImpl。配置方式如下:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service" ref="citationService" />
<property name="serviceName" value="CitationService" />
<property name="serviceInterface"
value="com.tickettodriver.CitationService" />
</bean>
service属性引用了citationService bean指定RmiServiceExporter将要此bean作为RMI服务导出。serviceName属性为RMI服务命名,serviceInterface属性指定了服务实现的接口。
默认情况下,RmiServiceExporter将会尝试在端口1099上绑定一个RMI注册表。如果1099上没有找到任何RMI注册表,RmiServiceExporter将会创建一个新的。如果你要绑定一个RMI注册表到一个新的端口或主机,你可以指定registryPort和registryHost属性值。例如:
<bean class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="service" ref="citationService" />
<property name="serviceName" value="CitationService" />
<property name="serviceInterface"
value="com.tickettodriver.CitationService" />
<property name="registryHost" value="rmi.tickettodrive.com" />
<property name="regestryPort" value="1199" />
</bean>
RMI可以很好地应用于远程服务交互,但是它有自己的局限。首先,RMI通讯时使用随机端口,而防火墙通常是不允许这么做的。其次,RMI是基于java的。这就意味着无论是客户端还是服务器端都需要用java编写。另外因为RMI使用了Java的序列化技术,所以经由网络发送的对象的类型必须在两端严格相同。
为此,人们开发了Hessian和Burlap来解决RMi在实际应用中的缺陷。