月蚀传说

浮躁让人失去理智
posts - 25, comments - 101, trackbacks - 0, articles - 0
  BlogJava :: 首页 ::  :: 联系 :: 聚合  :: 管理

SCA程序设计——ExternalService的应用

Posted on 2006-11-15 15:23 Dart 阅读(2463) 评论(7)  编辑  收藏 所属分类: SCA

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 )进行对应的调用,如图所示:


Binding_invoke.jpg


                                             图 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模块信息。当然,这些细节开发人员是不用关心的。


new_batloserver.JPG


然后新建一个
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


new_emfmodel.JPG

 


new_emfmodel2.JPG



完成操作后会生成一个新的

EMF Model 文件,打开这个文件的编辑器,选中根节点,在弹出菜单中选择 Set SDO Defaults


set_sdo_defautls.JPG


 

完成上述操作后,再在弹出菜单中选择 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_codes.JPG 

 

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 的就会很清楚了:


ip2location_wsdl.JPG


 

所以我们需要给 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 后得到以下的结果:


weather_result.JPG



小结

SCA ExternalService 可以说是一个Module对外调用的接口,它可以屏蔽远程传送的差异性,只要给出正确的BindingSCA容器就会成功调用远程的服务。ExternalService统一了SCA模块对外访问的方式,一个实现较好的SCA容器,将会支持多种Binding类型,比如EJBJMSRMI等,这样一来,在设计SCA程序的时候,只需要关心ExternalService的接口以及对应的数据结构,而那些如何去调用的技术细节将不会再困扰开发人员。

前段时间上网搜索一些关于SOA的资料,发现一个叫X极网的网站引用了我的<SCA程序设计——远程服务,以及远程服务实现的一些问题和想法>一文,特孙子,没有注明转载地址,还TM把标题给改了,鄙视一下!所以在这里我只想说:
                             转载注明原文地址,做人才厚道


评论

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2006-11-15 16:02 by FansOne
沙发

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2006-11-15 16:04 by lokvin[匿名]
支持一下,原创

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2006-11-25 21:09 by Dart
FansOne不够意思,抢个沙发就走人了

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2006-12-17 15:23 by 张如忠
写死了binding这不就耦合到一个固定的服务器上了吗,能不能做到不耦合呢?

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2006-12-18 09:52 by Dart
to 张如忠:

Binding的信息是在设计阶段写出来的,而且我的理解可能有错误,Binding的port在SCA规范中说是NameSpace+#wsdl.endpoing,而我直接作为一个WSDL URL路径使用了,所以我这种方法不一定正确。况且SCA还有专门的“部署”描述,我想很多和节点地址有关的信息(比如你所提到的)应该是在部署阶段才真正给出来的。

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2007-05-31 15:20 by 九片棱角的回忆
支持

# re: SCA程序设计——ExternalService的应用  回复  更多评论   

2007-06-08 15:37 by HEDY
globalweather这个webservice确实很孙子!
我之前也搞过这个webservice,没差点气死,返回的是xml,自己解析也就算了,由city查country倒还行,可以解析出来,由city查weather得到的xml怎么解析都出错,dom,jdom,dom4j等都用了,没成功过!最后还是自己写的一个解析方法,超级傻!

只有注册用户登录后才能发表评论。


网站导航: