1.
概述
在前面我已经讲了一些关于SCA的基础知识,使用了本人实现的一个SCA容器做为讲解示例,9月中旬我把这个SCA容器做为开源项目在Sourceforge.net上立项了,并且正式给这个SCA容器取名为Balto。
这一次我将继续使用Balto作为示例SCA容器,并讲一下SCA程序设计中的外部服务(ExternalService)
ExternalService
在
SCA
中可以被看作是一个
Module
的应用出口,它定义了
Module
所要调用的非
module
内部服务的外部服务信息,在SCA程序设计中的地位举足轻重。关于ExternalService的一些基本信息介绍,大家可以看一下我的另一篇文章.
ExternalService
虽然描述了外部服务的信息,但是它需要通过
Binding
来对该外部服务的访问细节进行描述。关于
Binding
的更多信息,也可以查看上面所说的那篇文章。
我们接下来将要讲的
ExternalService
都是基于
WebService Binding
的外部服务。
2
.ExternalService的
XML
格式
ExternalService
的定义需要写在
sca.module
文件中,具体格式如下:
<
externalService
name
="xs:NCName"
override
="sca:OverrideOptions"
?
>
*
<in
terface
.interface-type
/>
<
binding
.binding-type uri
="xs:anyURI"
/>
*
</
externalService
>
1)
先看
externalService
元素,该元素具有两个属性,一个是
name,
一个是
override
,
name
是标识
externalService
的名称的,在
ModuleContext
中通过
localService
定位服务的时候,是通过
name
属性所写的名称进行查询外部服务的。
2)
Interface
元素在之前的《本地服务》一文中有介绍,主要是指明该外部服务所对应的接口类型以及位置。一般情况下都使用
java
类型的接口:
<interface.java interface = “InterfaceClassName”>
3)
Binding 在下面会有讲解
3.
如何去构建一个可用的外部服务
既然是外部服务,有很大程度上都是属于异地节点上的服务,所以很多情况下我们使用外部服务都需要一些远程调用的手段对其进行调用,所以
ExternalService
也可以直接看作是一个
Remote Service
。
我们最常见的远程调用方式有以下几种:
RMI
、
EJB
、
Web Service
外部服务远程调用的绑定协议是由
Binding
元素给出的,目前
SCA
规范中给除了两种
Binding
,一种是
SCABinding
,这种
Binding
方式没有确切的说明;还有一种就是
WebService Binding
,顾名思义,这种
Binding
是基于
WebService
的一种远程调用
Binding
。
一旦
ExternalService
指定了明确的
Binding
方式后,在调该
ExternalService
指定的接口的方法的时候,
Balto SCA
容器就会通过
Java
的动态接口代理技术,生成一个动态的接口代理,然后通过
Binding
的类型以及
ExternalService
的一些信息细节,在动态接口代理方法中通过
Binding
对应的远程调用协议(比如
Web Service
)进行对应的调用,如图所示:
图 1
生成的不同的
InvokeHandler
会根据协议需要,解析出
Binding
类型中的信息细节,然后通过一些远程调用手段去调用该外部服务所在位置的服务实体。
说得不明不白的,举个
WebService Binding
的例子会清楚一些:
首先我们在
sca.module
文件中写入:
<
externalService
name
="WeatherProvider"
>
<
interface
.java interface
="net.x.webservice.client.WeatherProvider"
/>
<
binding
.ws port
="http://www.webservicex.net/globalweather.asmx?WSDL
#wsdl.endpoint(GlobalWeather/GlobalWeatherSoap)"
/>
</
externalService
>
这个
externalService
的含义是,我们讲通过一个
net.x.webservice.clinet.WeatherProvider
的接口类
,去调用一个
web servicec
,而这个
web service
是通过
binding.ws
来描述的,这个
web service
的
wsdl
的地址是
http://www.webservicex.net/globalweather.asmx?WSDL
,而且该外部服务调用的是这个
wsdl
中描述的
GlobalWeather
服务,并且这个服务在
wsdl
中的
binding
名是
GlobalWeatherSoap
。
也就是说,Web Service binding的port属性所需值的格式规范是这样的:
WSDL 1.1 : <WSDL-namespace-URI>#wsdl.endpoint(<service-name>/<port-name>)
WSDL 2.0 : <WSDL-namespace-URI>#wsdl.endpoint(<service-name>/<endpoint-name>)
我们可以利用API通过
SCA
的
ModuleContext
来定位这个外部服务:
ModuleContext.localService(“WeatherProvider”);
这段代码将
返回
net.x.webservice.clinet.WeatherProvider
接口。
不过这个接口是没有实现的,因为我们不可能在本地去实现这个接口类——我们根本就不知道这个服务的具体业务逻辑。我们只是通过这个接口类的方法调用,来确定所需要调用这个
Web Service
所要做的工作。
根据上面的图
1 可以清楚的知道,Balto SCA容器去定位一个外部服务的时候,当得到了该远程服务对应的Java接口后,会生成一个接口的动态代理,并且,通过这个远程服务的Binding信息以及一些调用方法信息(参数值,方法名),确定如何利用Axis2 Client去调用远程服务Binding到的那个Web Service。下面是动态代理处理用户调用方法的简要代码介绍:
public
Object excute(ExternalService externalService, Binding binding,
Object proxy, Method method, Object[] parameters)
throws
Throwable {
try
{
if
(binding
instanceof
WebServiceBinding) {
WebServiceBinding wsBinding
=
(WebServiceBinding) binding;
String wsdlURI
=
wsBinding.getWSDLNameSpaceURI();
WSDLFactory wsdlFactory
=
WSDLFactoryImpl.newInstance();
WSDLReader wsdlReader
=
wsdlFactory.newWSDLReader();
wsdlReader.setFeature(
"
javax.wsdl.verbose
"
,
true
);
Definition definition
=
wsdlReader.readWSDL(
null
,
wsdlURI);
String webServiceName
=
wsBinding.getServiceName();
String portName
=
wsBinding.getPortName();
……….
//
这是Axis2 Client的调用代码
ServiceClient service
=
new
ServiceClient();
Options options
=
new
Options();
service.setOptions(options);
EndpointReference targetEPR
=
new
EndpointReference(serviceURI);
options.setTo(targetEPR);
targetEPR.setAddress(serviceURI);
service.setTargetEPR(targetEPR);
…….
//
根据返回值类型来确定调用方式
if
(returnType
==
Void.
class
) {
service.sendRobust(omElement);
return
null
;
}
else
{
resultElement
=
service.sendReceive(omElement);
}
//
处理返回的SOAP体
………
}
这样做的目的是为了屏蔽掉开发人员在调用时候的一些细节处理,开发人员不关心整个
Web Service
的调用过程,只需要像调用简单
java
类一样调用即可。
4
.实战——天气预报
我们通过一个简单的例子来看看如何使用
Balto SCA
来进行做
ExternalService
。
示例工程下载
先考虑这么一个需求:
我们在登录一些网页的时候,网页上会显示出我们所在地当前的天气情况,这种比较个性化的功能常常能吸引不少网民的眼球。
问题是如何去实现呢?
首先,我们登录到某个网页上的时候,网站后台会得到我们的访问
IP
地址,通过这个地址是可以确定我们现在所在位置的。
然后,根据我们
IP
解析出来的物理位置,查询该地址最近的天气信息。
大概是这样去做。
但是我们怎么去解析
IP
地址获得物理位置呢?这种工作一般需要有一个存储了大量的
IP
地址到物理地址映射的数据库,一般情况下我们不可能拥有这样一个数据库。更何况,即使得到了物理地址,我们也不可能通过计算机去计算出当前的天气情况吧??
虽然我们不能做这些工作,但是在网络上存在这大量类似功能的
Web Service
。我们可以通过这些免费的
Web Service
来定制这么一个功能。
准备工作:安装
Eclipse WTP 1.0
,下载
Balto_tomcat_0_0_2
:
我在网上找到了两个
Web Service:
1.
获得
IP
地址和物理地址映射的
WebService:
http://ws.fraudlabs.com/ip2locationwebservice.asmx?wsdl
2.
获得天气情况的
Web Service
http://www.webservicex.net/globalweather.asmx?WSDL
现在需要通过
Balto SCA
将这两个
Web Service
做成
ExternalService
:
首先将
Balto_tomcat_test_0_9
添加到应用服务器中。这里需要说明一下,
Balto_tomcat_test_0_9
是整合在
tomcat
中的,就是说Balto = Tomcat,Balto替换了tomcat的启动Host入口类,所以在
tomcat
启动的时候
Balto 就会去解析部署的web application,当发现该web应用是一个SCA模块的话,就会对这个web application进行解析,并注册解析出的相关SCA模块信息。当然,这些细节开发人员是不用关心的。
然后新建一个
Dynamic Web project
,对应的
Target Runtime
选择刚设定好的
Tomcat
服务器。
接下来我们要将
Web Service
的
WSDL
中描述的复杂类型数据结构生成
Java
静态代码,并且必须是
SDO
类型的。这些复杂类型是提供给
ExternalService
指定的
Java
接口所需的调用参数以及返回结果使用的,因为大家都知道,
Web Service
在用
SOAP
传送过程中,
SOAP
的
Body信息体
内是采用的
XML
结构文档,并且在操作执行完毕后,
Web Service
的返回
SOAP
中,也是利用
XML
对结果进行描述的,所以
Balto
采用
SDO
作为复杂类型数据结构,不仅仅是因为
SCA
规范中的要求,更多的是为了更好地序列化、反序列化我们的复杂类型
(Java2X
ML,XML2Java)
。
先将上面提到的两个
WSDL
文件下载到本地,然后我们通过这两个
WSDL
生成一个
EMF Model
:
完成操作后会生成一个新的
EMF Model
文件,打开这个文件的编辑器,选中根节点,在弹出菜单中选择
Set SDO Defaults
:
完成上述操作后,再在弹出菜单中选择
Generate Model Code
,
Eclipse
就会自动生成一套
SDO
的模型代码,我们还要修改生成的SDO代码中的XXXPackageImpl的createExtendedMetaDataAnnotations方法,将代码中描述Element的name不正确的地方修改过来,并把创建EClass的地方所给出的ImplementClass的地方,将接口类替换成接口的实现类。上述步骤可以看一下《SCA程序设计——远程服务,以及实现远程服务的问题和想法》,其中有具体说明。
温馨小贴士:
Eclipse EMF生成的SDO代码中,用于描述XML的Element名以及对应Java类的XXXPackageImpl类,其中含有一个createExtendedMetaDataAnnotations方法,这个方法中描述了对应Java类以及Java类具有的属性所对应的XML中的Element以及Attribute的名称。但是一般利用XSD或者WSDL直接生成的SDO代码中,EMF会默认给出一个DocumentRoot的类,也就是说这个类才是EMF真正序列化java对象的根节点,如果不利用DocumentRoot包装我们的创建的SDO Java对象,序列化出来的XML就会出现XML名“不正确”的情况,而这种所谓“不正确”情况下Java对象对应的XML名,是在ExtendedMetaDataAnnotations中给出的。当然,上述情况只限于EMF生成的SDO.
将
SDO
代码生成好后,接下来就需要创建一个接口类。创建的这个接口类就是
ExternalService
所要指定的接口类。
这个接口类需要和ExternalService在Binding中给出的
WSDL
的
PortType
具有
相同的操作。使用过
Axis
或者
Axis2
的读者一定会联想到
Axis
以及
Axis2
提供给开发人员的
WSDL2Java
的工具,这个工具就是将
WSDL
生成一套
Axis
的客户端,包括复杂类型以及所要调用的
Web Service
对应的客户端
Stub
。
Balto
目前没有提供一个类似的工具(还在开发当中),所以这些工作还需要开发人员自己完成。
我们现在来为上面提到的查询
IP
对应物理位置的
ip2locationwebservice WSDL
创建一个
Interface
接口类:
我们给这个接口类取名为
IP2LocationWebService
,然后我们查看一个
ip2locationwebservice
的
WSDL
文件,
大家会发现这个
WSDL
文件中指定了
3
种
Binding
方式:
POST,GET
和
SOAP
,
Balto
目前只支持
SOAP
,所以我们只关心和
SOAP Binding
关联的
Port Type
:
Ip2LocationWebServiceSoap
。这个
Port Type
具有一个
Operation
(操作)
:IP2Location
,该操作的输入指向是的名为
IP2LocationSoapIn
的
Message
,而这个
Message
的
Element Type
是在
XSD Type
中定义的
IP2Location
类型。看看
WSDL
的就会很清楚了:
所以我们需要给
IP2LocationWebService
定义一个方法,方法名需要和
WSDL
的
Operation
名同名:
IP2Location
,而这个方法的输入参数应该是刚才所生成的
SDO
中的
IP2LocationTypeImpl
(注意:
Eclipse
通过
XSD
生成的
SDO
命名规则是一定的,
SDO
模型接口命名规则是属性名
+Type
;
SDO
模型接口实现命名规则是:属性名
+TypeImpl
),并且这个操作的返回值类型是一个
IP2LocationResponseTypeImpl
,代码如下:
public
interface
IP2LocationWebService {
IP2LocationResponseTypeImpl IP2Location(IP2LocationTypeImpl input);
}
这样一来我们就为
ip2locationwebservice
生成好了一个
Java
接口类,这个类将作为调用这个
Web Service
的客户端入口使用。
根据上面的介绍,我们可以根据同样的步骤为另一个
Web Service
:
globalweather
生成同样的
SDO
模型以及对应的
Java
接口:
public
interface
WeatherProvider {
GetCitiesByCountryResponseTypeImpl
GetCitiesByCountry(GetCitiesByCountryTypeImpl input);
GetWeatherResponseTypeImpl GetWeather(GetWeatherTypeImpl input);
}
折腾了半天,想必各位看官已经有点烦了。
我这里解释一下,其实上面的这些步骤都是由于鄙人的
Balto
目前还没有完成
WSDL2Java
工具所致,只能由开发人员手动完成。假以时日,待
Balto
完成了
WSDL2Java
工具后,上述的这些操作讲统统不复存在,只需要开发人员通过生成向导,点几下即可完成上述的复杂工作。
完成
SDO
以及
Java
接口生成后,我们就可以添加
SCA
的
ExternalService
了。
首先,我们在这个
Web Project
的
src
下新建一个
sca.module
文件。
这个文件是必须存在的,只有它存在
Balto
才会认为这个
Web porject
是一个
SCA
模块,否则将不会对其进行处理。
看一下
sca.module
文件:
<?
xml version="1.0" encoding="ASCII"
?>
<
module
xmlns
="http://www.osoa.org/xmlns/sca/0.9"
xmlns:v
="http://www.osoa.org/xmlns/sca/values/0.9"
name
="balto_weather_test"
>
<
externalService
name
="IP2LocationWebService"
>
<
interface
.java interface
="com.fraudlabs.ws.client.IP2LocationWebService" />
<
binding
.ws port
="http://ws.fraudlabs.com/ip2locationwebservice.asmx?wsdl
#wsdl.endpoint(Ip2LocationWebService/Ip2LocationWebServiceSoap)"
/>
</
externalService
>
<
externalService
name
="WeatherProvider"
>
<
interface
.java interface
="net.x.webservice.client.WeatherProvider"
/>
<
binding
.ws port
="http://www.webservicex.net/globalweather.asmx?WSDL
#wsdl.endpoint(GlobalWeather/GlobalWeatherSoap)"
/>
</
externalService
>
</
module
>
我们定义了两个外部服务,一个是查询
IP
对应物理位置的,一个是通过物理位置查询天气情况的。
现在我们可以直接使用它们。做一个测试使用的
Servlet
:
public
class
IP2AreaTestServlet
extends
javax.servlet.http.HttpServlet{
protected
void
doGet(HttpServletRequest request,
HttpServletResponse response)
throws
ServletException,
IOException {
String serverName
=
request.getServerName();
String localName
=
request.getLocalName();
//
获得访问者的IP地址
String remote
=
request.getRemoteAddr();
//
找到Ip查询物理位置的外部服务
IP2LocationWebService service1
=
(IP2LocationWebService) CurrentModuleContext.getContext().
locateService(
"
IP2LocationWebService
"
);
//
创建一个输入参数
IP2LocationType input
=
IP2LocationFactory.eINSTANCE
.createIP2LocationType();
//
给出IP地址
input.setIP(remote);
//
给一个License.这个License是该Web Service提供者给的一个免费版本的License,
// 可能有使用次数限制
//
如果大家想要一个新的,可以访问http:
//
www.fraudlabs.com
input.setLICENSE(
"
02-L68K-D95T
"
);
IP2LocationResponseTypeImpl result
=
service1
.IP2Location((IP2LocationTypeImpl) input);
IP2LOCATION location
=
result.getIP2LocationResult();
//
找到通过物理地址获得天气情况的外部服务
WeatherProvider provider
=
(WeatherProvider) CurrentModuleContext
.getContext().locateService(
"
WeatherProvider
"
);
GetWeatherType i
=
WebserviceFactory.eINSTANCE.createGetWeatherType();
i.setCityName(location.getCITY());
i.setCountryName(location.getCOUNTRYNAME());
String result2
=
provider.GetWeather((GetWeatherTypeImpl) i)
.getGetWeatherResult();
…….
}
}
整个调用过程就是上面代码所示,获得的天气情况就是
result2
变量中所记录的。
这里说一下,
IP2Location Web Service
是需要
License
的,上面所填写的License是本人申请的一个,可能会有使用次数限制,如果大家需要可以去他们的网站注册一个。
在
Servlet
中获得
Remote
是远程的
IP
地址,如果这个
Web Project
是在内网中被访问,那得到的
Remote
地址就会是内网的
IP
,比如
192.168.0.X
,这样的
IP
地址不一定能查出来物理地址的,反正我测试的时候,得到的
IP
地址是
168.1.100.X
,对应的物理地址是瑞士的某个城市。
还有就是
globalweather
这个
Web Service
的返回结果很孙子,直接返回一个
XML
结构的字符串,还要我们自己解析,并且给出的
encoding
还有一些问题。
我对返回的值处理了一下,将结果打印了出来,访问这个
Servlt
后得到以下的结果:
小结
在
SCA
中
ExternalService 可以说是一个Module对外调用的接口,它可以屏蔽远程传送的差异性,只要给出正确的Binding,SCA容器就会成功调用远程的服务。ExternalService统一了SCA模块对外访问的方式,一个实现较好的SCA容器,将会支持多种Binding类型,比如EJB、JMS、RMI等,这样一来,在设计SCA程序的时候,只需要关心ExternalService的接口以及对应的数据结构,而那些如何去调用的技术细节将不会再困扰开发人员。
前段时间上网搜索一些关于SOA的资料,发现一个叫X极网的网站引用了我的<SCA程序设计——远程服务,以及远程服务实现的一些问题和想法>一文,特孙子,没有注明转载地址,还TM把标题给改了,鄙视一下!所以在这里我只想说:
转载注明原文地址,做人才厚道