远程调用有很多种,例如:
1)远程方法调用(RMI)
2)Spring自己的HTTP invoker
3)EJB
4)Web Services
Rmi(remote method invocation,即远程方法调用),使用rmi,使用远程的方法就像使用本地的方法一样方便。Hessian和BlazeDS等提供了较好的对rmi的支持,但是它们都是基于sevlet的,也就是说要想使用Hessian或者BlazeDS就必须将编写的程序放入web容器中才能运行,在很多情况下是不方便的,比如说如果我们想要客户使用我们的程序,难道我们要求客户一定要安装tomcat这样的web容器不成。所以如何使用Spring和rmi结合编写javase的程序是下面讨论的重点。对于Hessian和BlazeDS的使用请参考我的其他文章。
要想编写一个RMI程序,首先需要编写的是服务接口(stub),服务接口是在客户端和服务端都必须有的部分,但是服务接口的实现仅在客户端存在。
下面给出一个实例程序:
1.0 服务端编写
先说服务端的编写,服务端先要提供一个远程访问的接口(就是stub):
package com.guan.springRmiDemo.server;
public interface HelloInterface {
public String sayHello(String name);
}
然后需要写至少一个类,实现上述的接口:
package com.guan.springRmiDemo.server;
import org.springframework.stereotype.Component;
@Component
public class Hello implements HelloInterface{
@Override
public String sayHello(String name) {
return "hello "+name;
}
}
需要写一个Spring的配置类(您可以使用xml形式的配置文档,我比较倾向于写配置类,有关配置类的说明可以看我的一篇关于这方面的博客)
package com.guan.springRmiDemo.server;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;
@Configuration
public class ServerConfigure {
//将实现stub的类注入
private @Autowired Hello hello;
//下面的bean将远程调用的接口暴露给外界客户端
@Bean
public RmiServiceExporter serviceExporter()
{
RmiServiceExporter rse = new RmiServiceExporter();
//rmi访问的名字是Hello
rse.setServiceName("Hello");
//rmi访问的端口号是1919
rse.setRegistryPort(1919);
//rmi暴露给外部的访问接口是HelloInterface接口
rse.setServiceInterface(HelloInterface.class);
//rmi实际使用的是Hello类的对象
rse.setService(hello);
return rse;
}
}
最后写一个main函数,启动这个rmi的监听器:
package com.guan.springRmiDemo.server;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ServerStart {
public static void main(String[] args) {
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
//扫描整个server目录
ctx.scan("com.guan.springRmiDemo.server");
//刷新
ctx.refresh();
}
}
注意,当初始化Spring环境后,Spring容器会自动的创建并处理监听(就是说,这种情况下执行ctx.refresh之后无法返回,除非你主动终止监听)。
2.0 客户端编写
客户端肯定也需要提供与服务端相同的接口:
package com.guan.springRmiDemo.client;
public interface HelloInterface {
public String sayHello(String name);
}
假设客户端中的某一个类中使用到了rmi的远程方法,如下:
package com.guan.springRmiDemo.client;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
@Component("clientUseHello")
public class ClientUseHello {
private HelloInterface hello;
//注意,这个服务的代理要以注入的形式赋值给hello变量
@Autowired
@Qualifier("helloProxy")
public void setHello(HelloInterface hello) {
this.hello = hello;
}
public void sayHello()
{
//使用的rmi远程服务
System.out.println(hello.sayHello("关新全"));
}
}
下面给出Spring的配置文件:
package com.guan.springRmiDemo.client;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
@Configuration
public class ClientConfigure {
//创建rmi的代理
@Bean(name="helloProxy")
public RmiProxyFactoryBean rmiProxy()
{
RmiProxyFactoryBean rpfb = new RmiProxyFactoryBean();
//访问rmi,ip,端口,和rmi名字
rpfb.setServiceUrl("rmi://202.194.158.128:1919/Hello");
//设置代理类代理的接口
rpfb.setServiceInterface(HelloInterface.class);
return rpfb;
}
}
上面有个小细节需要单独说:就是在Spring配置文档中创建的代理bean,实际是RmiProxyFactoryBean类型,而在ClientUseHello中注入的代理却是HelloInterface类型,这个类型转换是由Spring来完成的,由于在创建代理的时候,指定了代理的是HelloInterface接口,Spring可以将这个代理接口转换成我们使用的方式。(RmiProxyFactoryBean如何能转换成HelloInterface类型,是个很有意思的问题,可以深刻探讨下)。
最后给出客户端的测试代码:
package com.guan.springRmiDemo.client;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class ClientTest {
@Test
public void rmiTest()
{
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
ctx.scan("com.guan.springRmiDemo.client");
ctx.refresh();
ClientUseHello cuh = (ClientUseHello) ctx.getBean("clientUseHello");
cuh.sayHello();
}
}
执行结果:
hello 关新全
3. 一个灵活的客户端的实现
按照第二步中的方式实现的客户端是在spring容器启动之前就要确定链接的ip地址和端口号,有时我们需要更加灵活的rmi运行方式,比如,在运行时动态的决定需要访问的ip地址和端口号,这时,使用spring容器的方法就不可行,但是可以采用下面的方法来实现。
package com.guan.springRmiDemo.client;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;
public class OtherClientTest {
public static void main(String[] args) {
//创建远程代理
RmiProxyFactoryBean rpfb = new RmiProxyFactoryBean();
//可以根据需要动态设定rmi的ip地址和端口
rpfb.setServiceUrl("rmi://202.194.158.128:1919/Hello");
//设置访问接口
rpfb.setServiceInterface(HelloInterface.class);
//设置结束,让rmi开始链接远程的服务
rpfb.afterPropertiesSet();
//获取链接后的返回结果
Object o = rpfb.getObject();
//将结果转换成我们希望的类型
HelloInterface hello = (HelloInterface)o;
//调用结果执行远程调用
System.out.println(hello.sayHello("关新全"));
}
}