SpringMvc in Action——使用远程服务

Spring远程调用概览

远程调用是客户端应用和服务器端之间的会话。在客户端,它所需要的一些功能并不在该应用的实现范围之内,所以应用要向提供这些功能的其他系统寻求帮助,而远程应用通过远程服务暴露这些功能。
假如我们想把Spittr应用中的某些功能发布为远程服务,并提供给其他应用来使用。为了实现此想法,我们需要把SpitterService接口的基本功能发布为远程服务。
在这里插入图片描述
其他应用与Spittr之间的会话开始于客户端应用的一个远程过程调用(remote procedure call,RPC)。从表面上来看,RPC类似与调用本地对象的一个方法。这两者都是同步操作,会阻塞调用代码的执行,直到被调用的过程执行完毕。
Spring支持多种不同的RPC模型,包括RMI、Caucho的Hessian和Burlap以及Spring自带的HTTP invoker。
不管使用哪种模型,Spring都提供了风格一致的支持。这意味着一旦理解了如何配置Spring来使用其中的一种模型,如果我们决定使用另外一种模型的话,会拥有成本极低的学习曲线。

在所有的模型中,服务都作为Spring所管理的bean配置到我们的应用中。这是通过一个代理工厂bean实现的,这个bean能够把远程服务像本地对象一样装配到其他bean的属性中去。
在这里插入图片描述
客户端向代理发起调用,就像代理提供了这些服务一样。代理代表客户端与远程服务进行通信,由它负责处理连接的细节并向远程服务发起调用。
(代理模式)如果远程服务发生RemoteException异常,代理会处理此异常并且抛出非检查型异常RemoteAccessException。远程异常通常预示着系统发生了无法优雅恢复的问题。
在服务器端,我们可以使用表中所列出的任意一种模型将Spring管理的bean发布为远程服务。
在这里插入图片描述
无论是开发还是使用这些远程服务的代码,在Spring中,使用远程服务纯粹是一个配置问题,我们不需要编写任何Java代码就能支持远程调用。我们的服务bean也不需要关新它是否参与了一个RPC(当然,任何传递给远程调用的bean或从远程调用返回的bean可能需要实现一个java.io.Serializable接口)。

让我们通过RMI——Java最初的远程调用技术——开始探索Spring对远程调用的支持吧。

使用RMI

Spring简化了RMI模型,它提供了一个代理工厂bean,能够把RMI服务像本地JavaBean那样装配到我们的Spring应用中。
对于Spittr应用,让我们看看如何使用RMI到初七把SpitterService的实现发布为RMI服务。

服务器端导出RMI服务
在这里插入图片描述
当然我没有用过QAQ。


在Spring中配置RMI服务:
我们将要创建RMI服务需要发布SpitterService接口中的方法,如下程序清单展现了该接口的定义:

package spittr.service;

import spittr.bean.Spitter;
import spittr.bean.Spittle;

import java.util.List;

public interface SpitterService {
    List<Spittle> getRecentSpittles(int count);
    void saveSpittle(Spittle spittle);
    void saveSpitter(Spitter spitter);
    ...
}

如果我们使用传统的RMI来发布此服务SpitterService和SpitterServiceImpl中的所有方法都需要抛出java.rmi.RemoteException。但是我们使用Spring的RmiServiceExporter把该类转变为RMI服务,那现有的实现不需要做任何改变。
RmiServiceExporter可以把任意的Spring管理的bean发布为RMI服务,RmiServiceExporter把bean包装到一个适配器类中,然后适配器类被绑定到RMI注册表中,并且代理到服务类的请求(本例中服务类也就是SpitterServiceImpl)。
在这里插入图片描述
使用RmiServiceExporter将SpitterServiceImpl发布为RMI服务的最简单方式是在Spring中使用如下的@Bean方法进行配置:

@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService){
	RmiServiceExporter rmiExporter=new RmiServiceExporter();
	rmiExporter.setService(spitterService);
	rmiExporter.setServiceName("SpitterService");
	rmiExporter.setServiceInterface(SpitterService.class);
	return rmiExporter;
}

这里会把spitterService bean设置到service属性中,表明RmiServiceExporter要把该bean发布为一个RMI服务。serviceName属性命名了RMI服务,serviceInterface属性制定了此服务所实现的接口。默认情况下,RmiServiceExporter会尝试绑定到本地机器的1099端口上的RMI注册表,如果没有发现注册表,那会自己启动一个。
如果希望绑定到不同的端口或主机上的RMI注册表:(本例尝试绑定rmi.spitter.com主机的1199端口)

@Bean
public RmiServiceExporter rmiExporter(SpitterService spitterService){
	RmiServiceExporter rmiExporter=new RmiServiceExporter();
	rmiExporter.setService(spitterService);
	rmiExporter.setServiceName("SpitterService");
	rmiExporter.setServiceInterface(SpitterService.class);
	rmiExporter.setRegistryHost("rmi.spitter.com");
	rmiExporter.setRegistryPort(1199);
	return rmiExporter;
}

客户端装配RMI服务
以下是传统的RMI:
在这里插入图片描述
这段代码需要捕获和处理异常,而且含有样板代码。更糟糕的是,这段代码直接违反了DI原则。我们甚至没有任何机会去提供SpitterService对象的不同实现。
在理想的情况下,我们应可以为任意一个bean注入SpitterService对象,而不是让bean自己去查找服务。利用DI,SpitterSerivice的任何客户端都不需要关心此服务来源何处。

Spring的RmiProxyFactoryBean,是一个工厂bean,它可以为RMI服务创建代理:

@Bean
public RmiProxyFactoryBean SpitterService(){
	RmiProxyFactoryBean rmiProxy=new RmiProxyFactoryBean();
	rmiProxy.setServiceUrl("rmi://localhost/SpitterService");
	rmiProxy.setServiceInterface(SpitterService.class);
	return rmiProxy;
}

服务的URL是通过RmiProxyFactoryBean的ServiceUrl属性来设置的,在这里,服务器被设置为SpitterService,并且声明服务实在本地机器上的。同时,服务提供的接口由serviceInterface确定。
在这里插入图片描述
这样我们可以使用@Autowired注解把服务代理装配进客户端中:

@Autowired
SpitterService spitterService;

我们还可以像本地 bean一样调用它的方法:

public List<Spittle> getSpittles(String userName){
	Spitter spitter=spitterService.getSpitter(userName);
	return spitterService.getSpittlesForSpitter(spitter);
}

客户端代码不需要知道所处理的是一个RMI服务,它指数通过注入机制接受了一个SpitterService对象,根本不必关心它来自何处。
此外代理捕获了这个服务所有可能抛出的RemoteException异常,并把它包装为运行期异常重新抛出,这样我们就可以放心的忽略这些异常。我们也可以非常容易地把远程服务bean替换为该服务的其他实现——或许是不同的远程服务,或许是客户端测试的mock。

虽然客户端不必关心SpitterService是一个远程服务,但是!客户端不得不调用两次服务:一次是spitterService.getSpitter(userName),第二次是spitterService.getSpittlesForSpitter(spitter);,由于网络延迟影响,这会影响到客户端性能。
其二RMI是一种实现远程交互的好方法,但是它很难穿越防火墙:因为它能用任意端口来交互,这是防火墙通常不允许的。
其三RMI是基于Java的,这意味着客户端和服务端都必须是Java开发。因为RMI使用了Java的序列化机制。

所以我们还是希望有其他替代服务。
不过,我们先学习到这,以后会决定具体学习哪种远程服务。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值