一 简介
在Java开发中,我们可以使用多种远程调用技术,如:
- 远程方法调用(Remote Method Invocation,RMI)
- Caucho的Hessian和Burlap
- Spring基于HTTP的远程服务
- SOAP和RESTful风格的Web Service
注:关于Web Service可以自行参考我之前写过的相关文章(PS:www.zifangsky.cn/webservice)。在本篇文章中我主要介绍前面几种方式的具体代码实现
(1)远程调用与本地调用:
远程调用是客户端应用和服务端之间的会话。在客户端,它所需要的一些功能并不在该应用的实现范围之内,所以应用要向能提供这些功能的其他系统寻求帮助。同样,远程应用通过发布服务将这些功能暴露给其他系统使用
i)二者之间的相似点:
从表面上看,RPC调用(远程过程调用)类似于调用一个本地对象的某个方法。和本地方法调用相比两者都是同步操作,会阻塞调用代码的执行,直到被调用的过程执行完毕
ii)二者之间的不同点:
本地方法调用是指同一个应用中的两个代码块之间的执行流交换;RPC调用则是执行流从一个应用传递给另一个应用的过程,理论上另一个应用可以部署在跨网络的任意一台其他远程机器上
(2)Spring对多种远程调用的支持:
Spring支持多种不同的RPC模型,包括RMI、Hessian/Burlap以及Spring自带的Http Invoker。下面我将简单介绍一下它们之间的异同点:
RMI
:不考虑网络限制时使用(PS:因为RMI使用任意端口来交互,有时无法穿越防火墙)Hessian/Burlap
:考虑网络限制时,通过HTTP访问/发布基于Java的服务。Hessian是基于二进制的远程调用技术;而Burlap是基于XML的远程调用技术Spring的HttpInvoker
:跟Hessian/Burlap实现的调用技术类似,但是不同的是Hessian/Burlap使用了私有的对象序列化机制,而Spring的Http Invoker则使用的是Java的序列化机制
但是,不管选择哪种远程调用模型我们都会发现Spring提供了风格一致的支持。这意味着一旦理解了如何在Spring中配置和使用其中一种模型。那么当我们想要使用另外一种模型的话,将会变得非常容易
在所有的模型中,服务都作为Spring所管理的bean配置到我们的应用中。这是通过一个代理工厂bean实现的,这个bean能够把远程服务像本地对象一样装配到其他bean的属性中去。客户端向代理发起调用,就像代理提供了这些服务一样。代理代表客户端与远程服务进行通信,由它负责处理连接的细节并向远程服务发起调用。最后代理再返回远程服务执行完成之后的结果,至此整个调用过程完成
无论我们开发的是使用远程服务的代码,还是实现这些服务的代码,或者两者兼而有之。在Spring中,使用远程服务纯粹是一个配置问题。我们不需要编写任何Java代码就可以支持远程调用。我们的服务bean也不需要关心它们是否参与了一个RPC(PS:任何传递给远程调用的bean或从远程调用返回的bean可能需要实现java.io.Serializable 接口)
二 远程方法调用(RMI)
(1)定义一个暴露出来给其他系统调用的服务:
i)它的接口:
package cn.zifangsky.rmi.service;
import java.util.Date;
public interface TestRMIService {
/**
* 时间格式转化服务
* @param date 时间
* @return String类型的时间字符串
*/
public String formatDateService(Date date);
}
ii)它的实现类:
package cn.zifangsky.rmi.service.impl;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
import cn.zifangsky.rmi.service.TestRMIService;
@Component("testRMIServiceImpl")
public class TestRMIServiceImpl implements TestRMIService {
@Override
public String formatDateService(Date date) {
Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
if(date != null){
return format.format(date);
}else{
return "";
}
}
}
从上面的代码可以看出,这些都是简单的POJO,并没有依赖其他服务
(2)新建一个context_rmi.xml文件将上面定义的方法导出成远程服务:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.zifangsky.rmi.service.impl" />
<bean id="testRMIService" class="org.springframework.remoting.rmi.RmiServiceExporter">
<!-- RMI服务名 -->
<property name="serviceName" value="testRMIService"/>
<!-- RMI具体服务实现类 -->
<property name="service" ref="testRMIServiceImpl" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.rmi.service.TestRMIService" />
<!-- 注册端口 -->
<property name="registryPort" value="1099" />
</bean>
</beans>
可以看出,通过上面的XML配置导出了一个远程服务,其地址是:rmi://127.0.0.1:1099/testRMIService
注:上面的service的值“testRMIServiceImpl”是上面的TestRMIServiceImpl类的一个实例,通过@Component注解生成并注入到这里
(3)将TestRMIService接口等类导出成jar包供客户端使用:
上面定义好一个服务之后,我们要想在客户端(另一个Web项目)中使用这个服务,那么我们肯定是需要知道这个远程方法所在的类、方法名等信息才能够正常调用的。因此,接下来还需要将这些公共类打包成jar包放到客户端的lib目录才行
这一步可以手动选择导出的类,也可以使用一个Ant脚本来自动化完成这一操作。我这里使用的Ant脚本如下:
build_service.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project name="common_build" default="build_service" basedir=".">
<property name="targetdir" value="target"/>
<property name="classbase" value="WebContent/WEB-INF/classes/"/>
<property name="xpf.copy.info" value="true"/>
<property name="model.name" value="test"/>
<property name="env.name" value="dev"/>
<target name="build_service">
<jar destfile="${basedir}/target/zifangsky_${model.name}_RMIService_api.jar">
<fileset dir="${classbase}">
<include name="cn/zifangsky/**/model/*.class" />
<include name="cn/zifangsky/**/model/**/*.class" />
<include name="cn/zifangsky/**/service/*.class" />
<include name="cn/zifangsky/**/common/*.class" />
<include name="cn/zifangsky/**/common/**/*.class" />
</fileset>
</jar>
</target>
</project>
最后将生成的zifangsky_test_RMIService_api.jar放到客户端的lib目录下即可
(4)客户端的XML配置:
context_rmi_client.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.zifangsky.rmi.service" />
<bean id="testRMIServiceClient" class="org.springframework.remoting.rmi.RmiProxyFactoryBean">
<!-- 服务地址 -->
<property name="serviceUrl" value="rmi://127.0.0.1:1099/testRMIService" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.rmi.service.TestRMIService" />
</bean>
</beans>
这里的代码很简单,自己看注释就明白了
(5)测试:
package cn.zifangsky.test.rmi;
import java.util.Date;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.zifangsky.rmi.service.TestRMIService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/context/context.xml","classpath:/context/context_rmi_client.xml"})
public class TestRMI {
@Resource(name="testRMIServiceClient")
private TestRMIService testRMIService;
@Test
public void testFormatDate(){
Date date = new Date();
System.out.println("格式化后的时间是: " + testRMIService.formatDateService(date));
}
}
最后输出如下:
格式化后的时间是: 2017-01-08 15:55:49
从控制台的输出结果来看,这里的客户端代码的确调用了远程服务实现了时间格式的转化。到此,关于RMI的配置和使用就结束了,下面介绍的其他的几种远程调用的配置和使用跟RMI类似,因此我就简单叙述了
三 使用Hessian发布远程服务
Hessian和Burlap是Caucho Technology提供的两种基于HTTP的轻量级远程服务解决方案。借助于尽可能简单的API和通信协议,它们都致力于简化Web服务。
关于如何选择使用Hessian还是Burlap发布服务,很大程度上,它们是一样的。唯一的区别在于Hessian的消息是二进制的,而Burlap的消息是XML格式。因为Hessian的消息是二进制的,所以它在带宽上更具优势。但是如果我们更注重可读性(如出于调试的目的)或者我们的应用需要与没有Hessian实现的语言交互,那么Burlap的XML消息将会是更好的选择
(1)下载Hessian相关的jar包:
下载地址:hessian.caucho.com
(2)定义一个测试服务:
i)它的接口TestHessianService.java:
package cn.zifangsky.hessian.service;
public interface TestHessianService {
/**
* 测试接口
* @return
*/
public String sayHello();
}
ii)它的实现类TestHessianServiceImpl.java:
package cn.zifangsky.hessian.service.impl;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
import cn.zifangsky.hessian.service.TestHessianService;
@Component("testHessianServiceImpl")
public class TestHessianServiceImpl implements TestHessianService{
@Override
public String sayHello() {
Date date = new Date();
Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return "Hello,this's TestHessianService ---Time: " + format.format(date);
}
}
(3)导出成服务context_hessian.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.zifangsky.hessian.service.impl" />
<bean id="/testHessianService" class="org.springframework.remoting.caucho.HessianServiceExporter">
<!-- Hessian具体服务实现类 -->
<property name="service" ref="testHessianServiceImpl" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.hessian.service.TestHessianService" />
</bean>
</beans>
配置web.xml将所有的Hessian服务单独发布到一个目录:
<servlet>
<servlet-name>hessian</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:context/context_hessian.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>hessian</servlet-name>
<url-pattern>/hessian/*</url-pattern>
</servlet-mapping>
因此,上面的那个测试服务的地址是:http://localhost:9180/RMIServerDemo/hessian/testHessianService
(4)将服务接口导出成jar包供客户端使用:
操作步骤同上面的RMI的操作,略
(5)客户端的XML配置:
context_hessian_client.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<bean id="testHessianServiceClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<!-- 服务地址 -->
<property name="serviceUrl" value="http://localhost:9180/RMIServerDemo/hessian/testHessianService" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.hessian.service.TestHessianService" />
</bean>
</beans>
(6)测试:
package cn.zifangsky.test.hessian;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.zifangsky.hessian.service.TestHessianService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/context/context.xml","classpath:/context/context_hessian_client.xml"})
public class TestHessian {
@Resource(name="testHessianServiceClient")
TestHessianService testHessianService;
@Test
public void sayHello(){
System.out.println(testHessianService.sayHello());
}
}
最后输出如下:
Hello,this's TestHessianService ---Time: 2017-01-08 16:48:25
四 使用Burlap发布远程服务
(1)下载Burlap相关的jar包:
下载地址:repo1.maven.org/maven2/edu/…
(2)定义一个测试服务:
接口TestBurlapService.java:
package cn.zifangsky.burlap.service;
public interface TestBurlapService {
/**
* 测试接口
* @return
*/
public String sayHello();
}
其实现类TestBurlapServiceImpl.java:
package cn.zifangsky.burlap.service.impl;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
import cn.zifangsky.burlap.service.TestBurlapService;
@Component("testBurlapServiceImpl")
public class TestBurlapServiceImpl implements TestBurlapService{
@Override
public String sayHello() {
Date date = new Date();
Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return "Hello,this's TestBurlapService ---Time: " + format.format(date);
}
}
(3)导出成服务context_burlap.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.zifangsky.burlap.service.impl" />
<bean id="/testBurlapService" class="org.springframework.remoting.caucho.BurlapServiceExporter">
<!-- Burlap具体服务实现类 -->
<property name="service" ref="testBurlapServiceImpl" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.burlap.service.TestBurlapService" />
</bean>
</beans>
对应的web.xml配置:
<servlet>
<servlet-name>burlap</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:context/context_burlap.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>burlap</servlet-name>
<url-pattern>/burlap/*</url-pattern>
</servlet-mapping>
因此,上面的那个测试服务的地址是:http://localhost:9180/RMIServerDemo/burlap/testBurlapService
(4)将服务接口导出成jar包供客户端使用:
操作步骤同上面的RMI的操作,略
(5)客户端的XML配置:
context_burlap_client.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<bean id="testBurlapServiceClient" class="org.springframework.remoting.caucho.BurlapProxyFactoryBean">
<!-- 服务地址 -->
<property name="serviceUrl" value="http://localhost:9180/RMIServerDemo/burlap/testBurlapService" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.burlap.service.TestBurlapService" />
</bean>
</beans>
(6)测试:
package cn.zifangsky.test.burlap;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.zifangsky.burlap.service.TestBurlapService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/context/context.xml","classpath:/context/context_burlap_client.xml"})
public class TestBurlap {
@Resource(name="testBurlapServiceClient")
private TestBurlapService testBurlapService;
@Test
public void sayHello(){
System.out.println(testBurlapService.sayHello());
}
}
最后输出如下:
Hello,this's TestBurlapService ---Time: 2017-01-08 17:03:47
五 使用Spring的Http Invoker发布远程服务
(1)定义一个测试服务:
接口TestHttpInvokerService.java:
package cn.zifangsky.httpinvoker.service;
public interface TestHttpInvokerService {
/**
* 测试接口
* @return
*/
public String sayHello();
}
其实现类TestHttpInvokerServiceImpl.java:
package cn.zifangsky.httpinvoker.service.impl;
import java.text.Format;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.springframework.stereotype.Component;
import cn.zifangsky.httpinvoker.service.TestHttpInvokerService;
@Component("testHttpInvokerServiceImpl")
public class TestHttpInvokerServiceImpl implements TestHttpInvokerService {
@Override
public String sayHello() {
Date date = new Date();
Format format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return "Hello,this's TestHttpInvokerService ---Time: " + format.format(date);
}
}
(2)导出成服务context_invoker.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<context:component-scan base-package="cn.zifangsky.httpinvoker.service.impl" />
<bean id="/testHttpInvokerService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<!-- Http Invoker具体服务实现类 -->
<property name="service" ref="testHttpInvokerServiceImpl" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.httpinvoker.service.TestHttpInvokerService" />
</bean>
</beans>
对应的web.xml配置:
<servlet>
<servlet-name>invoker</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:context/context_invoker.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>invoker</servlet-name>
<url-pattern>/invoker/*</url-pattern>
</servlet-mapping>
因此,上面的那个测试服务的地址是:http://localhost:9180/RMIServerDemo/invoker/testHttpInvokerService
(3)将服务接口导出成jar包供客户端使用:
操作步骤同上面的RMI的操作,略
(4)客户端的XML配置:
context_httpinvoker_client.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.0.xsd">
<bean id="testHttpInvokerServiceClient" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean">
<!-- 服务地址 -->
<property name="serviceUrl" value="http://localhost:9180/RMIServerDemo/invoker/testHttpInvokerService" />
<!-- 服务调用接口 -->
<property name="serviceInterface" value="cn.zifangsky.httpinvoker.service.TestHttpInvokerService" />
</bean>
</beans>
(5)测试:
package cn.zifangsky.test.httpinvoker;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import cn.zifangsky.httpinvoker.service.TestHttpInvokerService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:/context/context.xml","classpath:/context/context_httpinvoker_client.xml"})
public class TestHttpInvoker {
@Resource(name="testHttpInvokerServiceClient")
private TestHttpInvokerService testHttpInvokerService;
@Test
public void sayHello(){
System.out.println(testHttpInvokerService.sayHello());
}
}
最后输出如下:
Hello,this's TestHttpInvokerService ---Time: 2017-01-08 17:09:51
至此,关于基于Spring的远程过程调用(RPC)的四种实现方式的介绍就全部结束了。从上面的代码可以看出,由于Spring的封装,这几种服务的发布和实现步骤都是很类似的,并且我们实际需要做的工作也变得非常简单,我们很大程度上只需要关注我们的具体业务逻辑就行了