您的位置:首页 > 理论基础 > 计算机网络

关于远程调用(XFire/HttpInvoker/Hessian etc.)及远程服务管理的一些随想

2011-11-27 08:08 465 查看

关于远程调用(XFire/HttpInvoker/Hessian etc.)及远程服务管理的一些随想

在现代
J2EE
企业应用系统中,存在着 Hessian
、 HttpInvoker
、 XFire
、 Axis
等多种形式的远程调用技术。尽管有 Spring
等框架对这些技术进行了封装,降低了使用的复杂度,但对普通程序员而言仍是复杂的——至少需要要掌握这些技术的基础知识。

无论使用那种技术,其基本原理都是一样的:服务端生成骨架,对外暴露服务;客户端生成服务代理,访问调用服务。通常情况下,生成服务代理的代价比较高昂,这也是我们第一次访问远程服务速度比较慢的原因,为每个请求生成新的服务代理恐怕不是我们所期望的。更何况,如果采用这种方式,就要在代码里针对各种不同的技术(如
XFire
、 HttpInvoker
)编写不同的服务生成和调用的处理代码。不仅麻烦,而且容易出错。我想,没有人愿意去直接操作各种框架技术的底层代码,这并不是一个好注意!

作为一种替代方案,我们设计了一个“服务池”的功能,或者说“服务工厂”更贴切一点。先看下面这张类图:



如上图所示,针对
HttpInvoker
、 XFire
、 Hessian
等各种远程调用技术,抽象出一个“远程服务池”(服务工厂)既
RemoteServicePool
接口。该接口提供了获取服务及一些其他的辅助功能,并针对
HttpInvoker
、 XFire
、 Hessian
等不同技术提供了相应的具体实现。采用这种方式,开发人员只需在代码中“注入”
RemoteServicePool
,并以统一的方式(如 getService()
)获取实际的服务,只是针对不同技术在配置上有些须差异而已。该技术的原理非常简单,在应用启动之前把所有存在的服务提供者提供的服务都配置好,并为它们分配一个唯一的
ID
。应用启动之后,框架会自动生成和这些地址相对应的服务代理(
ServiceProxy
),这些代理已经是可用的服务,服务获取的细节被完全屏蔽掉,开发者只要知道如何从
RemoteServicePool
中获取服务就可以了。看一下服务池的接口定义:
/**

*
远程服务缓冲池。

*

*
<p>

*
对于一个既定的服务契约(既接口),可以有很多服务提供者(
<b>
ServiceProvider
</b>
)。该接口的提出,是为了解决服务访问者和服务提供者之间

一对多
” 的关系。

*

*
@author
Tony

*/

public
interface
RemoteServicePool {

/**

*
从缓冲池中获取一个
<b>
Service
</b> 。

*

*
<p>

*
此处获得的是一个
<code>
Object
</code> ,需要调用者自己做类型转换。

*

*
<p>

*
参数
<b>
serviceId
</b> 代表服务缓冲池中的一个实例名称。服务类型采用配置文件中默认的类型。

*

*
@param
serviceId

*

实例名称

*

*
@return
服务对象

*/

Object getService(String serviceId);

}

接下来看看如何配置服务:
<bean id="userServicePool" class="com.
tonysoft
.common.XFireRemoteServicePool">
<property name="serviceInterface">

<value>com.
tonysoft
.demo.service.UserService</value>
</property>

<property name="serviceUrls">

<map>

<entry key=" server
1 ">

<value>http://localhost:8080/server1/service/userService?WSDL</value>

</entry>

<entry key="server2">

<value>http://localhost:8080/server2/service/userService?WSDL</value>

</entry>

</map>

</property>

</bean>

最后再来看一下访问服务的代码:
/**
服务工厂
*/

public
RemoteServicePool
userServicePool
;

/**

*
测试新增一个不存在的用户。

*/
public
void
testAddUser() {

UserService
userService
= null
;

try
{

userService
=(UserService)
userServicePool .getService("server2");

}
catch
(Exception e){

throw
new
RuntimeException(
"
获取服务失败,失败原因:" + e);

}

OperateResult result =
userService
.addUser( new
User(
"daodao" , "
技术部"
));

assertEquals(result.isSuccess(), true
);
}

该方案还为“双向关联”的系统服务提供了一个很好解决办法。看下面一张图:



如图,系统
B
和系统 C
都调用系统 A
进行付款操作;同时系统 A
要用远程服务向系统 B
或系统 C
进行认证操作,认证操作的接口(契约)都是一样的,业务逻辑可能有所差异。在这种情况下,配置在系统
A 中的认证服务就比较麻烦,因为要根据不同的系统调用认证服务,既从
B
过来的请求要访问 B
的认证服务,从 C
过来的请求要访问 C
的认证服务。用服务池可以很好的解决这个问题,把两个系统(
B 、
C )提供的认证服务地址都配置在同一个服务池中,根据不同的
ID
(如 B
、 C
)来决定使用那个系统的服务。

尽管服务池解决了一些问题,在某种程度上降低了复杂度,但仍存在如下一些问题:



服务的运行期动态注册



服务的自动注入(
IoC




透明化服务
ID
的传递

在服务池(
ServicePool
)概念的基础上进行扩展,我们得出了如下的系统模型:



在核心位置上是一个服务中心资源库(
ServiceRepository
),存储了系统中用到的所有的远程服务。服务采取动态注册的机制,由对外提供的服务注册器(
ServiceRegister
)提供服务注册功能。外部系统可以实现该接口向资源中心注册服务。提供了一个启动时运行的注册器,可以把静态配置在系统中的服务都注册进来。

服务的生成、管理等均由服务中心自己维护,委托服务代理生成器(
ServiceProxyGenerator
)完成服务的创建。可以针对现有的远程调用方式,如 XFire,HttpInvoker,Hessian
等创建服务代理,也可以针对自己定义的远程调用方式创建服务代理,由
CustomServiceProxyGenerator
完成该功能。

一个服务模型包括
5
个因素:



服务接口
serviceClass



服务
ID serviceId



服务类型
serviceType



服务地址
serviceUrl



附加属性
props
查找一个服务需要两个因素,一个是服务接口,另一个是服务
ID
。这两个因素共同决定了一个服务,既服务中心内部的“服务
ID ”。通过这种方式,可以允许存在多个
ID
相同但接口不同的服务,也可以存在多个接口相同但 ID
不同的服务。

服务
ID
的获取是系统中一个关键的功能,这部分对程序员来说应该是透明的,由系统自己维护。相应的提供了一个服务
ID 提供者
(ServiceIdProvider)
接口,由实现该接口的子类完成服务 ID
获取功能(这是比较关键的地方,需要特殊考虑)。

对于程序员来说,使用服务中心里的服务再也不能比这样再简单了!看看配置:
<
bean
id
= "helloHttpInvokerService"
parent
=
"abstractServiceProxyFactory"
>

<
property
name
=
"serviceInterface"
>

<
value
>
com.tonysoft.common.service.repository.example.HelloHttpInvoker
</
value
>

</
property
>

</
bean
>
再看如何使用这个
bean

private
HelloHttpInvoker
helloHttpInvokerService
;

public
void
testHttpInvoker() {

assertNotNull(
"helloHttpInvokerService can't be null !"
, helloHttpInvokerService
);

assertEquals ( "Hello , HttpInvoker !"
, helloHttpInvokerService
.sayHello());

}

/**

*
@param
helloHttpInvokerService

*

the
helloHttpInvokerService
to
set

*/

public
void
setHelloHttpInvokerService(HelloHttpInvoker helloHttpInvokerService) {

this
.
helloHttpInvokerService
= helloHttpInvokerService;

}

就是这样的简单!
Spring
会把这个 bean
自动注入到程序中,可以象使用其他任何 bean
一样使用它!程序员完全不用关心该服务由谁提供、采用什么技术,他只要知道系统中存在这样一个服务就
OK
了。该技术彻底向程序员屏蔽了底层技术的实现细节,以统一的方式访问任何形式的远程服务。至于服务是如何生成、如何配置的将在后面叙述。

服务(
Service Bean
)是如何实现自动注入( IoC
)的呢?
注意到上面配置的
bean
都继承了“
abstractServiceProxyFactory
”,它是一个工厂 bean
,负责根据给定的接口类型,到服务中心( ServiceRepository
)查找服务,并生成服务代理。我们来看一下它的核心代码:
/**

*
服务代理工厂。

*

*
<p>

*
该工厂对程序员屏蔽了服务实现的技术细节,对于
XFire

Hessian 、
HttpInvoker
等常用远程服务形式进行封装。

*

*
<p>

*
程序员只需要提供一个服务接口(契约),该工厂会从服务中心
<code>
ServiceRepository
</code>
中查找符合该接口的远程服务实例。

*

*
<p>

*
查找的规则是由服务
ID 提供者所提供的服务
ID
和服务接口名字共同组成的服务关键字匹配。

*

*
@author
Tony

*/

public
class
ServiceProxyFactory
implements
FactoryBean {

/**
服务中心
*/

private
ServiceRepository
serviceRepository
;

/**
服务
ID 提供者
*/

private
ServiceIdProvider
serviceIdProvider
;

/**
服务接口
*/

private
Class
serviceInterface
;

/*

* @see org.springframework.beans.factory.FactoryBean#getObject()

*/

public
Object getObject()
throws
Exception {


return
ProxyFactory.getProxy(getObjectType(),
new
ServiceProxyInterceptor());

// return serviceRepository.getService(serviceInterface, serviceIdProvider.getCurrentServiceId());

}

/*

* @see org.springframework.beans.factory.FactoryBean#getObjectType()

*/

public
Class getObjectType() {

return
serviceInterface
;
}

/*

* @see org.springframework.beans.factory.FactoryBean#isSingleton()

*/

public
boolean
isSingleton() {

return
true
;

}

/*

*
远程服务代理拦截器。

*/

private
class
ServiceProxyInterceptor
implements
MethodInterceptor {

/*

* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)

*/

public
Object invoke(MethodInvocation invocation)
throws
Throwable {

Method method = invocation.getMethod();

Object[] args = invocation.getArguments();

Object client = getClient();

return
method.invoke(client, args);

}

private
Object getClient() {

try
{

return

serviceRepository
.getService(
serviceInterface
, serviceIdProvider

.getCurrentServiceId());

}
catch
(ServiceException e) {

//
TODO

e.printStackTrace();

return

null
;

}

}

}

// ----
容器自动注入
----

······

我们注意上面加粗、倾斜的那段代码:
return

serviceRepository
.getService(
serviceInterface
, serviceIdProvider

.getCurrentServiceId());

真正的魅力就在这个地方。根据服务接口类型和服务
ID
,从服务中心获取特定的服务。服务接口是配置好的,
而服务 ID
则在运行时才能确定,根据不同的应用、不同的策略提供不同的
ServiceIdProvider
。其中用到了 Spring
的 FactoryBean
和拦截器,至于为什么要在这里使用拦截器,可以参考 Spring
框架的源码。

服务代理生成器(
ServiceProxyGenerator
)也是一个值得一提的地方,我们先看一下它的接口:
/**

*
服务代理生成器。

*

*
@author
Tony

*/

public
interface
ServiceProxyGenerator {

/**

*
取得服务代理对象。

*

*
@param
serviceClass

*

服务接口

*
@param
serviceUrl

*

服务地址

*
@param
props

*

附加属性

*
@return
代理对象

*
@throws
Exception

*/

Object getService(Class serviceClass, String serviceUrl, Properties props)
throws
Exception;

}

它只有一个
getService()
方法,那么为什么设计这个接口?在什么地方使用呢?回答这个问题之前先来看看下面这段代码:
public
void
registService(ServiceModel serviceModel)
throws
ServiceException {

······


String key = serviceModel.getServiceId() +
KEY_SPAR

+ serviceModel.getServiceClass().getName();

if
(
serviceNames
.contains(key)) {

throw
new
ServiceRegistException(
"service is exist!"
);
}

Object proxy =
null
;

try
{


ServiceProxyGenerator proxyGenerator = (ServiceProxyGenerator)
beanFactory

.getBean(serviceModel.getServiceType() +
PROXY_GENERATOR_END
);


proxy = proxyGenerator.getService(serviceModel.getServiceClass(), serviceModel

.getServiceUrl(), serviceModel.getProps());

}
catch
(Exception e) {

throw
new
ServiceRegistException(
"can't regist service !"
, e);

}

if
(proxy !=
null
) {

serviceNames
.add(key);

serviceContainer
.put(key, proxy);

}
else
{

throw
new
ServiceRegistException(
"fail to regist service !"
);
}

}

上面做特殊标记的代码就是应用服务代理生成器的地方,这里我们用到了
Spring
的 bean
工厂,根据注册服务的类型( xfire,httpinvoker,hessian
等)到
Spring 容器里查找相应的生成器,并生成指定类型的服务。看下面配置的几个服务代理生成器:
<!-- XFire
类型服务代理生成器
-->

<
bean
id
= "xfire_generator"
class
=
"com.tonysoft.common.service.repository.generator.XFireServiceProxyGenerator"
lazy-init
=
"true"
>

<
property
name
=
"serviceFactory"
>

<
ref
bean
=
"xfire.serviceFactory" />

</
property
>

</
bean
>

<!-- Hessian
类型服务代理生成器
-->

<
bean
id
= "hessian_generator"
class
=
"com.tonysoft.common.service.repository.generator.HessianServiceProxyGenerator"
lazy-init
=
"true"
>

</
bean
>

<!-- HttpInvoker
类型服务代理生成器
-->

<
bean
id
= "httpinvoker_generator"
class
=
"com.tonysoft.common.service.repository.generator.HttpInvokeServiceProxyGenerator"
lazy-init
=
"true"
>

</
bean
>

<!--
自定义
类型服务代理生成器
-->

<
bean
id
= "custom_generator"
class
=
"com.tonysoft.common.service.repository.generator.CustomServiceProxyGenerator"
lazy-init
=
"true"
>

</
bean
>

<!--
服务中心(资源库)
-->

<
bean
id
= "serviceRepository"
class
=
"com.tonysoft.common.service.repository.DefaultServiceRepository"
>

</
bean
>

<!--
服务
ID 提供者
-->

<
bean
id
= "serviceIdProvider"
class
=
"com.tonysoft.common.service.repository.provider.DefaultServiceIdProvider"
>

</
bean
>

<!--
所有远程服务的基础类
-->

<
bean
id
= "abstractServiceProxyFactory"
class
=
"com.tonysoft.common.service.repository.ServiceProxyFactory" abstract
=
"true"
>

</

bean
>
简单看一下
HttpInvoker
类型服务代理生成器的代码:

public
class
HttpInvokeServiceProxyGenerator
implements
ServiceProxyGenerator {

/**
HttpInvoker
服务代理工厂
*/

private
HttpInvokerProxyFactoryBean
httpInvokerFactory
= new
HttpInvokerProxyFactoryBean();

/*

* @see com.alipay.xfiredemo.common.ServiceProxyGenerator#getService(java.lang.Class, java.lang.String,

* java.util.Properties)

*/

public
Object getService(Class serviceClass, String serviceUrl, Properties props) {

// Todo initial httpInvokerFactory with props

httpInvokerFactory
.setServiceInterface(serviceClass);

httpInvokerFactory
.setServiceUrl(serviceUrl);

// must invoke this method

httpInvokerFactory
.afterPropertiesSet();

return
httpInvokerFactory
.getObject();

}

}

是的,正如你所看到的一样,我们这里把真正生成服务代理的任务交给了
Spring

HttpInvokerProxyFactoryBean
来完成。

提供在初始化时注册的静态服务功能,配制如下:

<!--
初始化时注册的静态服务
-->

<
bean
id
= "bootupServiceRegister"
class
=
"com.tonysoft.common.service.repository.register.BootupServiceRegister" lazy-init
=
"false"
>

<
property
name
=
"services"
>

<
list
>

<!--

<bean class="com.tonysoft.common.service.repository.ServiceModel">

<property name="serviceClass"><value>com.tonysoft.common.service.repository.example.HelloHessian</value></property>

<property name="serviceId"><value>default</value></property>

<property name="serviceType"><value>hessian</value></property>

<property name="serviceUrl"><value>http://localhost:8080/serviceRepositoryApplication/service/hessian/helloHessian.service</value></property>

<property name="props">

<props></props>

</property>

</bean>

-->

<
bean
class
=
"com.tonysoft.common.service.repository.ServiceModel"
>

<
property
name
=
"serviceClass"
><
value
> com.tonysoft.common.service.repository.example.HelloHttpInvoker
</
value
></
property
>

<
property
name
=
"serviceId"
><
value
> default
</
value
></
property
>

<
property
name
=
"serviceType"
><
value
> httpinvoker
</
value
></
property
>

<
property
name
=
"serviceUrl"
><
value
> http://localhost:8080/serviceRepositoryApplication/service/httpInvoker/helloHttpInvoker.service </
value
></
property
>

<
property
name
=
"props"
>

<
props
></
props
>

</
property
>

</
bean
>

<
bean
class
=
"com.tonysoft.common.service.repository.ServiceModel"
>

<
property
name
=
"serviceClass"
><
value
> com.tonysoft.common.service.repository.example.HelloXFire
</
value
></
property
>

<
property
name
=
"serviceId"
><
value
> default
</
value
></
property
>

<
property
name
=
"serviceType"
><
value
> xfire
</
value
></
property
>

<
property
name
=
"serviceUrl"
><
value
> http://localhost:8080/serviceRepositoryApplication/service/xfire/helloXFire.service?WSDL </
value
></
property
>

<
property
name
=
"props"
>

<
props
></
props
>

</
property
>

</
bean
>

</
list
>

</
property
>

</

bean
>
具体内容可以参看附件中的资源:

一、
ServiceRepository
的源代码( Eclipse
工程)
二、
一个示例应用

三、
打包部署的
ANT
脚本

把项目导入
Eclipse
中,直接运行 Ant
脚本,在 target
目录下会生成服务中心的 jar
包,同时生成示例应用的 war
包,把 war
包放到任意服务器( Server
)上并启动服务器并确保应用正常启动。
运行
ServiceRepositoryTest
.java
执行完整的单元测试,观测结果。其他的自己看源码吧。

下载附件

转自:http://www.duduwolf.com/wiki/2007/325.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: