1.?
概述
在前面我已經(jīng)講了一些關(guān)于SCA的基礎(chǔ)知識,使用了本人實現(xiàn)的一個SCA容器做為講解示例,9月中旬我把這個SCA容器做為開源項目在Sourceforge.net上立項了,并且正式給這個SCA容器取名為Balto。
這一次我將繼續(xù)使用Balto作為示例SCA容器,并講一下SCA程序設(shè)計中的外部服務(wù)(ExternalService)
ExternalService
在
SCA
中可以被看作是一個
Module
的應(yīng)用出口,它定義了
Module
所要調(diào)用的非
module
內(nèi)部服務(wù)的外部服務(wù)信息,在SCA程序設(shè)計中的地位舉足輕重。關(guān)于ExternalService的一些基本信息介紹,大家可以看一下我的另一篇文章.
ExternalService
雖然描述了外部服務(wù)的信息,但是它需要通過
Binding
來對該外部服務(wù)的訪問細節(jié)進行描述。關(guān)于
Binding
的更多信息,也可以查看上面所說的那篇文章。
我們接下來將要講的
ExternalService
都是基于
WebService Binding
的外部服務(wù)。
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
定位服務(wù)的時候,是通過
name
屬性所寫的名稱進行查詢外部服務(wù)的。
2)?????
Interface
元素在之前的《本地服務(wù)》一文中有介紹,主要是指明該外部服務(wù)所對應(yīng)的接口類型以及位置。一般情況下都使用
java
類型的接口:
<interface.java interface = “InterfaceClassName”>
3)?????
Binding?在下面會有講解
3.
如何去構(gòu)建一個可用的外部服務(wù)
既然是外部服務(wù),有很大程度上都是屬于異地節(jié)點上的服務(wù),所以很多情況下我們使用外部服務(wù)都需要一些遠程調(diào)用的手段對其進行調(diào)用,所以
ExternalService
也可以直接看作是一個
Remote Service
。
我們最常見的遠程調(diào)用方式有以下幾種:
RMI
、
EJB
、
Web Service
外部服務(wù)遠程調(diào)用的綁定協(xié)議是由
Binding
元素給出的,目前
SCA
規(guī)范中給除了兩種
Binding
,一種是
SCABinding
,這種
Binding
方式?jīng)]有確切的說明;還有一種就是
WebService Binding
,顧名思義,這種
Binding
是基于
WebService
的一種遠程調(diào)用
Binding
。
一旦
ExternalService
指定了明確的
Binding
方式后,在調(diào)該
ExternalService
指定的接口的方法的時候,
Balto SCA
容器就會通過
Java
的動態(tài)接口代理技術(shù),生成一個動態(tài)的接口代理,然后通過
Binding
的類型以及
ExternalService
的一些信息細節(jié),在動態(tài)接口代理方法中通過
Binding
對應(yīng)的遠程調(diào)用協(xié)議(比如
Web Service
)進行對應(yīng)的調(diào)用,如圖所示:
?????????????????????????????????????????????圖 1
生成的不同的
InvokeHandler
會根據(jù)協(xié)議需要,解析出
Binding
類型中的信息細節(jié),然后通過一些遠程調(diào)用手段去調(diào)用該外部服務(wù)所在位置的服務(wù)實體。
說得不明不白的,舉個
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
的接口類
,去調(diào)用一個
web servicec
,而這個
web service
是通過
binding.ws
來描述的,這個
web service
的
wsdl
的地址是
http://www.webservicex.net/globalweather.asmx?WSDL
,而且該外部服務(wù)調(diào)用的是這個
wsdl
中描述的
GlobalWeather
服務(wù),并且這個服務(wù)在
wsdl
中的
binding
名是
GlobalWeatherSoap
。
也就是說,Web Service binding的port屬性所需值的格式規(guī)范是這樣的:
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
來定位這個外部服務(wù):
ModuleContext.localService(“WeatherProvider”);
這段代碼將
返回
net.x.webservice.clinet.WeatherProvider
接口。
不過這個接口是沒有實現(xiàn)的,因為我們不可能在本地去實現(xiàn)這個接口類——我們根本就不知道這個服務(wù)的具體業(yè)務(wù)邏輯。我們只是通過這個接口類的方法調(diào)用,來確定所需要調(diào)用這個
Web Service
所要做的工作。
根據(jù)上面的圖
1 可以清楚的知道,Balto SCA容器去定位一個外部服務(wù)的時候,當?shù)玫搅嗽撨h程服務(wù)對應(yīng)的Java接口后,會生成一個接口的動態(tài)代理,并且,通過這個遠程服務(wù)的Binding信息以及一些調(diào)用方法信息(參數(shù)值,方法名),確定如何利用Axis2 Client去調(diào)用遠程服務(wù)Binding到的那個Web Service。下面是動態(tài)代理處理用戶調(diào)用方法的簡要代碼介紹:
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的調(diào)用代碼
????????????????????????????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);
??????????????????????????? …….
??????????????????????????? //
?根據(jù)返回值類型來確定調(diào)用方式
??????????????????????????? if
?(returnType?
==
?Void.
class
)?{
???????????????????????????????????service.sendRobust(omElement);
???????????????????????????????????
return
?
null
;
????????????????????????????}?
else
?{
???????????????????????????????????resultElement?
=
?service.sendReceive(omElement);
????????????????????????????}
?????????????????????????? //
??處理返回的SOAP體
??????????????????????????? ………
???????}
?
這樣做的目的是為了屏蔽掉開發(fā)人員在調(diào)用時候的一些細節(jié)處理,開發(fā)人員不關(guān)心整個
Web Service
的調(diào)用過程,只需要像調(diào)用簡單
java
類一樣調(diào)用即可。
4
.實戰(zhàn)——天氣預(yù)報
我們通過一個簡單的例子來看看如何使用
Balto SCA
來進行做
ExternalService
。
示例工程下載
先考慮這么一個需求:
我們在登錄一些網(wǎng)頁的時候,網(wǎng)頁上會顯示出我們所在地當前的天氣情況,這種比較個性化的功能常常能吸引不少網(wǎng)民的眼球。
問題是如何去實現(xiàn)呢?
首先,我們登錄到某個網(wǎng)頁上的時候,網(wǎng)站后臺會得到我們的訪問
IP
地址,通過這個地址是可以確定我們現(xiàn)在所在位置的。
然后,根據(jù)我們
IP
解析出來的物理位置,查詢該地址最近的天氣信息。
大概是這樣去做。
但是我們怎么去解析
IP
地址獲得物理位置呢?這種工作一般需要有一個存儲了大量的
IP
地址到物理地址映射的數(shù)據(jù)庫,一般情況下我們不可能擁有這樣一個數(shù)據(jù)庫。更何況,即使得到了物理地址,我們也不可能通過計算機去計算出當前的天氣情況吧??
雖然我們不能做這些工作,但是在網(wǎng)絡(luò)上存在這大量類似功能的
Web Service
。我們可以通過這些免費的
Web Service
來定制這么一個功能。
準備工作:安裝
Eclipse WTP 1.0
,下載
Balto_tomcat_0_0_2
:
我在網(wǎng)上找到了兩個
Web Service:
1.??????
獲得
IP
地址和物理地址映射的
WebService:
???http://ws.fraudlabs.com/ip2locationwebservice.asmx?wsdl
2.??????
獲得天氣情況的
Web Service
???http://www.webservicex.net/globalweather.asmx?WSDL
現(xiàn)在需要通過
Balto SCA
將這兩個
Web Service
做成
ExternalService
:
首先將
Balto_tomcat_test_0_9
添加到應(yīng)用服務(wù)器中。這里需要說明一下,
Balto_tomcat_test_0_9
是整合在
tomcat
中的,就是說Balto = Tomcat,Balto替換了tomcat的啟動Host入口類,所以在
tomcat
啟動的時候
Balto 就會去解析部署的web application,當發(fā)現(xiàn)該web應(yīng)用是一個SCA模塊的話,就會對這個web application進行解析,并注冊解析出的相關(guān)SCA模塊信息。當然,這些細節(jié)開發(fā)人員是不用關(guān)心的。
然后新建一個
Dynamic Web project
,對應(yīng)的
Target Runtime
選擇剛設(shè)定好的
Tomcat
服務(wù)器。
接下來我們要將
Web Service
的
WSDL
中描述的復(fù)雜類型數(shù)據(jù)結(jié)構(gòu)生成
Java
靜態(tài)代碼,并且必須是
SDO
類型的。這些復(fù)雜類型是提供給
ExternalService
指定的
Java
接口所需的調(diào)用參數(shù)以及返回結(jié)果使用的,因為大家都知道,
Web Service
在用
SOAP
傳送過程中,
SOAP
的
Body信息體
內(nèi)是采用的
XML
結(jié)構(gòu)文檔,并且在操作執(zhí)行完畢后,
Web Service
的返回
SOAP
中,也是利用
XML
對結(jié)果進行描述的,所以
Balto
采用
SDO
作為復(fù)雜類型數(shù)據(jù)結(jié)構(gòu),不僅僅是因為
SCA
規(guī)范中的要求,更多的是為了更好地序列化、反序列化我們的復(fù)雜類型
(Java2X
ML,XML2Java)
。
先將上面提到的兩個
WSDL
文件下載到本地,然后我們通過這兩個
WSDL
生成一個
EMF Model
:
?
完成操作后會生成一個新的
EMF Model
文件,打開這個文件的編輯器,選中根節(jié)點,在彈出菜單中選擇
Set SDO Defaults
:
?
完成上述操作后,再在彈出菜單中選擇
Generate Model Code
,
Eclipse
就會自動生成一套
SDO
的模型代碼,我們還要修改生成的SDO代碼中的XXXPackageImpl的createExtendedMetaDataAnnotations方法,將代碼中描述Element的name不正確的地方修改過來,并把創(chuàng)建EClass的地方所給出的ImplementClass的地方,將接口類替換成接口的實現(xiàn)類。上述步驟可以看一下《SCA程序設(shè)計——遠程服務(wù),以及實現(xiàn)遠程服務(wù)的問題和想法》,其中有具體說明。
溫馨小貼士:
Eclipse?EMF生成的SDO代碼中,用于描述XML的Element名以及對應(yīng)Java類的XXXPackageImpl類,其中含有一個createExtendedMetaDataAnnotations方法,這個方法中描述了對應(yīng)Java類以及Java類具有的屬性所對應(yīng)的XML中的Element以及Attribute的名稱。但是一般利用XSD或者WSDL直接生成的SDO代碼中,EMF會默認給出一個DocumentRoot的類,也就是說這個類才是EMF真正序列化java對象的根節(jié)點,如果不利用DocumentRoot包裝我們的創(chuàng)建的SDO?Java對象,序列化出來的XML就會出現(xiàn)XML名“不正確”的情況,而這種所謂“不正確”情況下Java對象對應(yīng)的XML名,是在ExtendedMetaDataAnnotations中給出的。當然,上述情況只限于EMF生成的SDO.
?
?
?
將
SDO
代碼生成好后,接下來就需要創(chuàng)建一個接口類。創(chuàng)建的這個接口類就是
ExternalService
所要指定的接口類。
這個接口類需要和ExternalService在Binding中給出的
WSDL
的
PortType
具有
相同的操作。使用過
Axis
或者
Axis2
的讀者一定會聯(lián)想到
Axis
以及
Axis2
提供給開發(fā)人員的
WSDL2Java
的工具,這個工具就是將
WSDL
生成一套
Axis
的客戶端,包括復(fù)雜類型以及所要調(diào)用的
Web Service
對應(yīng)的客戶端
Stub
。
Balto
目前沒有提供一個類似的工具(還在開發(fā)當中),所以這些工作還需要開發(fā)人員自己完成。
我們現(xiàn)在來為上面提到的查詢
IP
對應(yīng)物理位置的
ip2locationwebservice WSDL
創(chuàng)建一個
Interface
接口類:
我們給這個接口類取名為
IP2LocationWebService
,然后我們查看一個
ip2locationwebservice
的
WSDL
文件,
大家會發(fā)現(xiàn)這個
WSDL
文件中指定了
3
種
Binding
方式:
POST,GET
和
SOAP
,
Balto
目前只支持
SOAP
,所以我們只關(guān)心和
SOAP Binding
關(guān)聯(lián)的
Port Type
:
Ip2LocationWebServiceSoap
。這個
Port Type
具有一個
Operation
(操作)
:IP2Location
,該操作的輸入指向是的名為
IP2LocationSoapIn
的
Message
,而這個
Message
的
Element Type
是在
XSD Type
中定義的
IP2Location
類型。看看
WSDL
的就會很清楚了:
?
所以我們需要給
IP2LocationWebService
定義一個方法,方法名需要和
WSDL
的
Operation
名同名:
IP2Location
,而這個方法的輸入?yún)?shù)應(yīng)該是剛才所生成的
SDO
中的
IP2LocationTypeImpl
(注意:
Eclipse
通過
XSD
生成的
SDO
命名規(guī)則是一定的,
SDO
模型接口命名規(guī)則是屬性名
+Type
;
SDO
模型接口實現(xiàn)命名規(guī)則是:屬性名
+TypeImpl
),并且這個操作的返回值類型是一個
IP2LocationResponseTypeImpl
,代碼如下:
?
public
?
interface
?IP2LocationWebService?{
???????IP2LocationResponseTypeImpl?IP2Location(IP2LocationTypeImpl?input);
}
?
這樣一來我們就為
ip2locationwebservice
生成好了一個
Java
接口類,這個類將作為調(diào)用這個
Web Service
的客戶端入口使用。
根據(jù)上面的介紹,我們可以根據(jù)同樣的步驟為另一個
Web Service
:
globalweather
生成同樣的
SDO
模型以及對應(yīng)的
Java
接口:
public
?
interface
?WeatherProvider?{
???????GetCitiesByCountryResponseTypeImpl?
????????????????????? GetCitiesByCountry(GetCitiesByCountryTypeImpl?input);
???????GetWeatherResponseTypeImpl?GetWeather(GetWeatherTypeImpl?input);
}
?
折騰了半天,想必各位看官已經(jīng)有點煩了。
我這里解釋一下,其實上面的這些步驟都是由于鄙人的
Balto
目前還沒有完成
WSDL2Java
工具所致,只能由開發(fā)人員手動完成。假以時日,待
Balto
完成了
WSDL2Java
工具后,上述的這些操作講統(tǒng)統(tǒng)不復(fù)存在,只需要開發(fā)人員通過生成向?qū)Вc幾下即可完成上述的復(fù)雜工作。
完成
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
>
?
我們定義了兩個外部服務(wù),一個是查詢
IP
對應(yīng)物理位置的,一個是通過物理位置查詢天氣情況的。
現(xiàn)在我們可以直接使用它們。做一個測試使用的
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查詢物理位置的外部服務(wù)
??????????????IP2LocationWebService?service1?
=
?
???????????????????????? (IP2LocationWebService)?CurrentModuleContext.getContext().
??????????????????????????????????????????????? locateService(
"
IP2LocationWebService
"
);
??????????????
//
?創(chuàng)建一個輸入?yún)?shù)
??????????????IP2LocationType?input?
=
?IP2LocationFactory.eINSTANCE
????????????????????????????.createIP2LocationType();
??????????????
//
?給出IP地址
??????????????input.setIP(remote);
???????????? ?
//
?給一個License.這個License是該Web?Service提供者給的一個免費版本的License,
??????????????// 可能有使用次數(shù)限制
??????????????
//
?如果大家想要一個新的,可以訪問http:
//
www.fraudlabs.com?
??????????????input.setLICENSE(
"
02-L68K-D95T
"
);
??????????????IP2LocationResponseTypeImpl?result?
=
?service1
????????????????????????????.IP2Location((IP2LocationTypeImpl)?input);
??????????????IP2LOCATION?location?
=
?result.getIP2LocationResult();
??????????????
//
?找到通過物理地址獲得天氣情況的外部服務(wù)
??????????????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();
??????????????…….
???????}
}
?
整個調(diào)用過程就是上面代碼所示,獲得的天氣情況就是
result2
變量中所記錄的。
這里說一下,
IP2Location Web Service
是需要
License
的,上面所填寫的License是本人申請的一個,可能會有使用次數(shù)限制,如果大家需要可以去他們的網(wǎng)站注冊一個。
在
Servlet
中獲得
Remote
是遠程的
IP
地址,如果這個
Web Project
是在內(nèi)網(wǎng)中被訪問,那得到的
Remote
地址就會是內(nèi)網(wǎng)的
IP
,比如
192.168.0.X
,這樣的
IP
地址不一定能查出來物理地址的,反正我測試的時候,得到的
IP
地址是
168.1.100.X
,對應(yīng)的物理地址是瑞士的某個城市。
還有就是
globalweather
這個
Web Service
的返回結(jié)果很孫子,直接返回一個
XML
結(jié)構(gòu)的字符串,還要我們自己解析,并且給出的
encoding
還有一些問題。
我對返回的值處理了一下,將結(jié)果打印了出來,訪問這個
Servlt
后得到以下的結(jié)果:
小結(jié)
在
SCA
中
ExternalService 可以說是一個Module對外調(diào)用的接口,它可以屏蔽遠程傳送的差異性,只要給出正確的Binding,SCA容器就會成功調(diào)用遠程的服務(wù)。ExternalService統(tǒng)一了SCA模塊對外訪問的方式,一個實現(xiàn)較好的SCA容器,將會支持多種Binding類型,比如EJB、JMS、RMI等,這樣一來,在設(shè)計SCA程序的時候,只需要關(guān)心ExternalService的接口以及對應(yīng)的數(shù)據(jù)結(jié)構(gòu),而那些如何去調(diào)用的技術(shù)細節(jié)將不會再困擾開發(fā)人員。
前段時間上網(wǎng)搜索一些關(guān)于SOA的資料,發(fā)現(xiàn)一個叫X極網(wǎng)的網(wǎng)站引用了我的<SCA程序設(shè)計——遠程服務(wù),以及遠程服務(wù)實現(xiàn)的一些問題和想法>一文,特孫子,沒有注明轉(zhuǎn)載地址,還TM把標題給改了,鄙視一下!所以在這里我只想說:
???????????????????????????? 轉(zhuǎn)載注明原文地址,做人才厚道