本文主要介绍如何实现将 spring-hessian 集成在gwt 系统中,完成系统的前后台模块的分离部署。集成了 hessian 后,可以实现将业务逻辑代码从原有系统中剥离,并部署到另外一个容器中,同时将这些业务逻辑代码发布成远程接口,原有系统的前台模块则通过调用这些远程接口实现原有的业务服务。这样实现可以很好的分担原服务器的压力,同时降低了前后台模块的耦合度,使向外提供高效的业务服务接口成为现实(区别于 webservice )。
首先介绍简单介绍一下hessian,Hessian 是一个轻量级的 remoting on http 工具,使用简单的方法提供了 RMI ( Remote Method Invocation ,远程方法调用)的功能。采用的是二进制 RPC ( Remote Procedure Call Protocol ,远程过程调用协议)协议,因为采用的是二进制协议,所以它很适合于发送二进制数据。它提供 web 服务就像创建 Servlet 一样简单 . 使用服务就像使用 JDK 代理接口一样简单。
hessian 类似于 Webservice ,但是它不使用 soap 协议,它把协议报文封装到 http 封包中,通过 HTTP 信道传输。是一种高效简洁的远程调用框架,它采用的是二进制 RPC 协议( Binary ),具有轻量、传输量小、平台无关的特点,特别适合于目前网络带宽比较小的手机网络应用项目。 Hessian 是通过 servlet 提供远程服务,完全使用动态代理来实现的 , 推荐采用面向接口编程,因此, Hessian 服务建议通过接口暴露。 hessian 已经支持 Java , Flash/Flex , Python , C++ , .NET C# , D , Erlang , PHP , Ruby , Objective C 。它不需要我们与 xml “ 打交道 ” ,直接操作对象,省去了很多麻烦。
基于 gwt 开发的 系统提供了一套默认的基于 servlet 开发的 RPC 服务。 它能够让我们直接在前后台传递对象 ,而无需考虑中间的传递过 程。为开发提供了很大的便利。
事实上,当我们在前台模块定义一个 model 对象并作为参数传递给某个 service 时,就是发送了一个 servlet 请求,同时将该对象序列化为二进制流,发送到 RPC 接口中。之后再 RPC 接口中反序列化该对象并通过请求的 service 名反射后台业务服务进行业务处理,并将最终结果序列化传送回前台并显示。流程图如下:
Gwt 提供的 RPC 服务方便了我们的开发,但是他不支持将前后台模块分离部署,观看源码可知, GWT 的 rpc 服务不支持传递的请求来自不同的 web 服务器:
/**
* The servlet base class for your RPC service implementations that
* automatically deserializes incoming requests from the client and serializes
* outgoing responses for client/server RPCs.
*/
public class RemoteServiceServlet extends AbstractRemoteServiceServlet
implements SerializationPolicyProvider {
static SerializationPolicy loadSerializationPolicy(HttpServlet servlet,
HttpServletRequest request, String moduleBaseURL, String strongName) {
// The request can tell you the path of the web app relative to the
// container root.
String contextPath = request.getContextPath();
String modulePath = null;
if (moduleBaseURL != null) {
try {
modulePath = new URL(moduleBaseURL).getPath();
} catch (MalformedURLException ex) {
// log the information, we will default
servlet.log("Malformed moduleBaseURL: " + moduleBaseURL, ex);
}
}
SerializationPolicy serializationPolicy = null;
注:
/*
* Check that the module path must be in the same web app as the servlet
* itself. If you need to implement a scheme different than this, override
* this method.
*/
。。。。
所以,本文确认使用集成 hessian 的方式来实现远程调用业务实现来完成前后台模块的分离。通过 hessian 提供的 rpc 方式,可以在 gwt rpc 中将反射调用服务接口的路径改成调用远程 hessian 提供的远程服务接口,通过该接口在反射调用对应的服务来进行相应的逻辑处理并返回序列化后的结果对象,在前台进行展现。流程图如下:
1.1进入 eclipse ,新建一个 web 工程,本例中以我测试的gwt工程为基础,创建名为 TMHessianServer 的工程
1.2 将gwt工程的业务层代码都复制到该工程中
1.3在上步中检查各种实体 bean ,使其继承了序列化接口,这一步很重要,由于 hessian 需要将对象进行序列化,所以如果不这么做会产生异常
1.4打开
web.xml
,添加
hessian
的
servlet
配置。
Hessian
将每个服务都发不成一个
servlet,
前台模块可以通过该
servlet
调用远程服务
<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:Hessian-servlet.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>
1.5根据创建的 hessian servlet 名创建对应的配置文件。该配置文件命名是以 xml 里 hessian servlet 名 + “ -servlet ”为规则。该例中使用 hessian-servlet.xml 为 hessian 配置文件。将该配置文件放在 classpath 中。本例放在如下位置
1.6打开 hessian-servlet.xml, 将远程医疗中的业务 bean 定义全部复制到其中。并根据每个 service bean 定义远程服务。如下举例
<!-- 业务类 -->
<bean id="patientService" class="net.carefx.tm.manager.service.impl.PatientServiceImpl">
<property name="patientMapper" ref="patientMapper"></property>
</bean>
<!-- 远程服务 -->
<bean name="/patientService"
class="org.springframework.remoting.caucho.HessianServiceExporter">
<property name="service" ref="patientService" />
<property name="serviceInterface">
<value>
net.carefx.tm.manager.service.PatientService
</value>
</property>
</bean>
1.7 将gwt工程中中业务层的 spring 的配置文件拷贝到新建的业务工程中
1.8 在新工程的 xml 文件中添加 spring 配置以及业务文件的引用
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:framework-common.spring.xml,
classpath:framework-cache.spring.xml,
classpath:pms/pms-db.spring.xml,
classpath:pms/pms-manager.spring.xml,
classpath:pms/pms-service-local.spring.xml,
classpath:tm/tm-db.spring.xml,
classpath:Hessian-servlet.xml
</param-value>
</context-param>
<!-- spring 读取 log4jConfigLocation -->
<listener>
<listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<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:Hessian-servlet.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>
1.9将所有相关的 jar 包都放入 web-inf/lib 目录下。至此 hessian 工程配置完成。启动该工程。新发布的远程服务远程地址应该为 heep://ip:port/ProjectName/serviceName. 如上例中的 patientservice 的地址即为: http://localhost:8080/TMHessianServer/patientService
2.1打开源工程系统,将所有业务相关的类删除。注意:由于开发时的不注意或者业务需要,有些业务类被前台引用,造成了紧耦合,不能删除(比如多线程控制等),为了尽量少的更改源代码可以将这些类保留,但是实际上最终执行可以再远程服务的工程里进行。
2.2 打开系统中的业务 bean 配置文件,本例中为 tm-manager.spring.xml 。修改原有的业务 bean 定义,修改为支持 hessian 的远程调用定义
<bean id="patientService"
class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="serviceUrl"
value="${tm.patientService}" />//远程服务地址
<property name="serviceInterface" //服务接口
value="net.carefx.tm.manager.service.PatientService" />
</bean>
2.3新建属性文件 tm-service-location.properties ,将上步中的远程服务地址保存在内
2.4至此配置完成,启动远程医疗系统,同时启动远程服务系统 TMHessianServet ,将两个系统分置于不同的容器中,进行测试,如无异常,证明配置成功
1. nested exception is com.caucho.hessian.io.HessianProtocolException: expected hessian reply at end of file
解决方案:这是因为接口中出现方法重载,在调用时,服务器端会跑出异常。
在整合 spring 中,在客户端的配置里面加上“ <property name="overloadEnabled" value="true"></property> ”代码可以解决。如下:
<bean id="doctorService"
class="org.springframework.remoting.caucho.HessianProxyFactoryBean">
<property name="overloadEnabled" value="true"></property>
<property name="serviceUrl"
value="${tm.doctorService}" />
<property name="serviceInterface" value="net.carefx.tm.manager.service.DoctorService" />
</bean>
2.com.caucho.hessian.client.HessianRuntimeException: com.caucho.hessian.io.HessianProtocolException: expected integer at 0x53 java.lang.String
解决方案:检查 hessian 包是否存在服务器端和客户端不一致,或者是服务器自带了不同版本的 hessian.jar 包 。统一 jar 包可以解决