??xml version="1.0" encoding="utf-8" standalone="yes"?>
?nbsp; void assertNoFault(Document node)Q确认SOAP响应报文无错误;
?nbsp; java.util.List assertValid(java.lang.String xpath, java.lang.Object node)Q确认在DOM节点特定路径下有对应的元素,路径通过XPath表达式进行定义,该方法还匹配的元素以List对象q回Q用户可以对匚w的元素进行进一步检验;
?nbsp; void assertXPathEquals(java.lang.String xpath, java.lang.String value, Document node)Q确认特定\径DOM节点为某一特定|
?nbsp; assertInvalid(java.lang.String xpath, java.lang.Object node)Q确认DOM节点特定路径下未包含元素?/p>
AbstractXFireSpringTest是AbstractXFireTest的子c,在Spring中用户只需要扩展该cdƈ实现该类的抽象方法ApplicationContext createContext()Q就可以对Spring容器中用XFire定义的Web Serviceq行试了?/p>
Z试Web ServiceQ我们必d备一个SOAPh报文Q用户可以简单地手工~写一个,或通过SOAP报文截取工具Q如前面我们介绍的TcpTrace、SOAPScope、Apache Axis的TCPMon{)获得一些可用的SOAPh报文。代码清?6-6是一个访问BbtForumService 服务的请求SOAP报文Q?/p>
代码清单16-6 request_soap.xmlQSOAP报文层面试
我们其保存在request_soap.xml文g中,攄在类路径com/baobaotao/xfire/server下。当该SOAPh报文发送给BbtForumService的Web Service后,我们预计它应该返回如代码清单16-7所C的正确的SOAP响应报文Q?/p>
代码清单16-7 SOAP响应报文
下面Q我们着手编写测试BbtForumService Web Service的测试类Q以验证实际SOAP响应报文是否和代码清?6-7中的一P
代码清单16-8 TestBbtForumService
使用AbstractXFireSpringTest试Web Service首先要做的第一件事是通过实现createContext()Ҏ构造Spring容器Q如①所C。当Spring容器启动ӞXFire自动让容器中的Web Service生效Q仅q行试Q不能对外提供服务)?/p>
W二步需要向Web Service发送一个SOAPh报文以得C个SOAP响应报文Q如②所C。接下来Q通过AbstractXFireTest提供的检DOM内容的方法对报文q行正确性验证。由于代码清?6?的SOAP报文体中对应?lt;getRefinedTopicCountResponse>元素及内部元素都位于http://www.baobaotao.com命名I间中,报文体中没有个命名空间定义相应的别名Qؓ了在后箋断言Ҏ中能够用简单的方式定义XPath表达式,我们在④处ؓhttp://www.baobaotao.com命名I间定义了一个别名?/p>
理解以上试代码中几个断aҎ的关键在于理解XPath表达式语aQXPath语法内容很丰富,不可能在q里逐一讲解Q我们只介绍一些典型的XPath语法以满_见的试需求:
??#8220;/”为前~的\径表CZDOM根\径开始,?#8220;/soap:Envelope/soap:Body”Q?/p>
??#8220;//”为前~的\径表CZDOML元素开始查询,?#8220;//out”表示L元素为out的元素;
?元素的属性通过@attrName表示Q如“//xsd:complexType[@name=""Book""]”表示DOM中Q意元素名为complexTypeq且拥有一个gؓBook的name属性的元素Q?/p>
?元素的值通过text()表示Q如“//test:Response[text()='32']”表示DOM中Q意gؓ32、元素名为ResponseQ且位于test命名I间中的元素?/p>
现在回过头来看⑤、⑥两处的断aҎQ相信大家就可以很容易地理解断言规则了,⑤处的断aSOAP响应报文是否包含某一特定元素Q而⑥处的断言则对元素内的D行检。我们也可以通过printNode()Ҏ一个节点输出到控制CQ以便于查看?/p>
能够不启动Web服务器的情况下通过客户端程序测试Web Service的功能,q一崭新的测试方法对于开发h员来说一定颇具吸引力。因为,q意味着用户可以完全在IDE环境中运行测试,不需要外部环境的支持。不qn受这一试好处的应用必M证客L和服务端的Web Service都位于同一JVM中,q时h报文和响应报文直接在JVM内部通道中传输。当使用JVM内部通道传输h和响应的SOAP报文Ӟ我们只需要调整服务的地址可以了Q?/p>
代码清单16-9 TextBbtForumService JVM模式试
以上代码中,①处的服务地址采用了JVM模式的地址Q和其对应的HTTP地址则是http://localhost:8080/baobaotao/service/BbtForumServiceQ所以只需要将服务名前的部分替换ؓ“xfire.local://”可以了?/p>
下面?/span>XFire客户端调用的程分析图,本文后箋本分围l该囑ֱ开?/span>
XFire客户端的调用非常灉|Q可以有很多U方式,如通过配置调用、通过API~程调用或者与Spring{?/span>IoC框架集成使用。虽然调用方式灵zdP但万变不d中,其内部流E是一致的?/span>
服务模型?/span>XFire中非帔R要的概念之一Q包含了服务的接口信息、操作信息?/span>Binding信息{诸多服务调用过E中需要的信息。因此在q行服务调用之前首先要创建服务模型。创建服务模型的工作是由服务工厂ServiceFactory完成的,用户需要ؓ服务工程提供服务接口、名U、命名空间等一些信息,其中服务接口是必ȝQ其他ؓ可选信息?/span>
Client?/span>XFire客户端的核心l成部分Q间接的代表了一个服务。当为具体某个服务配|拦截器Q?/span>HandlerQ有很多U译法如拦截器、处理器、过滤器{,本文l一用拦截器Q时Q其实是拦截器信息应用?/span>Client实例上?/span>Client可以手工创徏也可以由XFireProxyFactory创徏Q无论通过哪种方式Q?/span>Client在初始化q程中最重要的一步都是在out拦截器堆栈中增加一?/span>OutMessageSender拦截器。该拦截器负责最l将服务调用通过HTTP发送到服务提供者ƈq回处理l果。本文后l部分还会对OutMessageSender做更加详l的讲解?/span>
XFireProxyQ?/span>XFire SOAP客户端代理实玎ͼ用户调用服务Ӟ?/span>Hello.echo(“tony”)Q就是通过该对象的invokeҎ来执行。实际上Q?/span>XFireProxy只是调用代理到Client实例Q最l执行服务的q是Client实例?/span>
Client实例?/span>invokeҎ在执行时Q生成了一?/span>Invocation对象Q该对象构造了一ơ完整的调用信息Q包?/span>OutMessage?/span>MessageContext{。同?/span>Invocationq负责构造一个拦截器道Q?/span>HandlerPipelineQ,该管道包含了本次调用需要执行的所有拦截器Q当然也包括OutMessageSender。这些拦截器会分不同的阶D|执行Q这也是XFire一个特性?/span>XFire默认定义了很多阶D(PhaseQ,每个阶段都会有若q拦截器被调用?/span>
拦截器(HandlerQ是XFire中最为重要的概念Q一ơ服务调用就是由若干拦截器组合完成的?/span>XFire默认提供了很多预定义的拦截器Q用户也可以定义自己的拦截器。基本上Q通过拦截器可以媄?/span>XFire执行q程中的M步骤Q你可以为所ƲؓQ)
拦截器有两个重要的概念,一个是阶段Q?/span>PhaseQ,一个是序Q?/span>OrderQ。这两个因素共同军_了拦截器的执行顺序。可以在三个不同的地斚w|拦截器Q?/span>
n XFire实例Q全局拦截器,Ҏ有通道上的所有服务v作用
n TransportQ通道特定的拦截器Q只对该通道Q如HTTP?/span>JMSQv作用
n 具体服务Q服务特定的拦截器,只对该服务v作用
其实Q具体服务上的拦截器最l是配置?/span>Client上。对于同一个阶D上的拦截器Q执行顺序ؓ“具体服务?/span>>Transport?/span>>XFire实例”。千万不要忽视这些顺序,q对你正的使用拦截器非常有帮助?/span>
q是整个调用链中最后的一环,也是最关键的一步?/span>OutMessageHandlerQ前文已l有所提及Q是一个特D的拦截器,?/span>Client初始化时创徏q加入调用链中。该拦截器处于拦截器调用铄Phase.SEND阶段Q基本上也是最后的阶段?/span>OutMessageHandler从当前调用的消息上下文(MessageContextQ中获取h的服务地址URI以及SOAP消息Q然后通过HTTP?/span>SOAPh发送到q程服务器(针对HTTP通道Q如果是JMS通道则发送到指定的目的地Q。最l将q程服务器的响应逐q回l调用者?/span>
前文很多地方都提?/span>Handler非常重要Q那么具体有那些应用场景呢?本部分通过两个案例逐步演示Handler的应用?/span>
一?span style="font: 7pt 'Times New Roman'">
单安全验?/span>q是一个非常典型的应用场景Q假?/span>A公司对外提供了一个旅E信息查询服务,该服务通过XFire对外发布。但?/span>A公司只希望其合作伙伴才能使用该服务,那么A公司可以服务配置一?/span>HandlerQ该Handler?/span>SOAP的消息头中获取认证字W串Q只有通过验证的请求才被执行。下面是单的CZ代码Q真实情况要比这复杂得多?/span>
publicvoid invoke(MessageContext context) throws Exception { Element header = context.getInMessage().getHeader(); String authCode = header.getChild("authCode",null).getValue(); if(!"tony".equals(authCode)){ thrownew XFireFault("Authentication Fail!", XFireFault.SENDER); } } |
对于A公司的合作伙_要想调用该服务,必须在其SOAP的消息头中包含上面代码中的验证字W串Q否则服务将被拒l。下面是单的CZ代码Q?/span>
publicvoid invoke(MessageContext context) throws Exception { Element header = context.getInMessage().getHeader(); Element authCode = new Element("authCode"); authCode.addContent("tony"); header.addContent(authCode); } |
二?span style="font: 7pt 'Times New Roman'">
查找真实服务q是一个比较特D的应用场景Q假?/span>A公司已经初步实现SOAQ拥有一个服务注册中心,所有的XFire服务都在该中心注册。客L在调用服务时需要动态的从该服务注册中心获取当前的服务地址及版本。通过其他方式肯定也可以实现该需求,但是通过Handler来实C非常的幽雅,而且对应用不需要做M变动。我们先来看一?/span>Handler的代码:
publicvoid invoke(MessageContext context) throws Exception { // 1.d lookupRealServiceUri(context); } privatevoid lookupRealServiceUri(MessageContext context) { String uri = context.getOutMessage().getUri(); try { uri = serviceLocator.lookup(requestEnvironment, uri); } catch (Exception e) { // Ignoral this exception } context.getOutMessage().setUri(uri); } |
正如代码所C,只需要从context中获取当前请求的服务URI地址Q然后用当前h环境信息及服?/span>URI地址到服务注册中心查扄实的服务Qƈ重新讄服务的地址?/span>
本文_略的介l了XFire客户端的调用程Qƈ着重讲解了Handler的扩展机制及其应用场景,力求读者能够通过本文?/span>XFire能有更加深入的了解和掌握。文中难免存在不之处,Ƣ迎M形式的交?/span>
二、基工作
1.开发环?br />
我用axis做ؓWeb
Service引擎Q它是Apache的一个开源web service引擎。它目前最为成熟的开源web
service引擎之一。下面我主要介绍一下如何用Axis搭徏web service 服务的环境?
①安装tomcat5.0应用服务器(也可以装5.5Q不q我一直在?.0Q?
②解压下载(
http://ws.apache.org/AxisQ后的axis包,包中axis目录复制到tomcat目录下的webapps目录下;
③将axis/WEB-INF/lib目录下类文g复制到tomcat目录下的common/lib目录下;
④重新启动tomcat,讉Khttp://localhost:8080/axis/happyaxis.jsp,如果能访问,表示安装成功Q?
注意Qaxis有几个可选的包,如email.jar....Q你可以找来攑ֈtomcat目录下的common/lib目录下,如果不用相关的功能也可以不用?
q样Q开发环境就搭徏好了?
2.如何部vWeb Service
部v有三U方式:Dynamic Invocation
Interface(DII)、Stubs方式、Dynamic Proxy方式Q这里就介绍一下简单,也是我用的方式QDII?
DII方式中,先写好服务的JAVA文gQ假讑字ؓhelloworld.javaQ,然后把它Q注意是源文Ӟ拯到webapps/axis目录
中,后缀Ҏjws(此时文g名ؓQhelloworld.jws)Q然后访问连接http://localhost:
8080/Axis/helloworld.jws?wsdlQ页面显CAxis自动生成的wsdlQ这样一个Web
Service部|好了。怎么P是不是很?
我的计数器服务就是以q种方式部v的,下文中我会只说将计数器服务部|好Q你可不要说Q怎么部vQ我不会呀。那在古代就要被打手板了。所以我惻I古代只学四书五经也是件好事呀。现在要学这么多东西Q半天学不会Q手要被打烂了?
三、计数器服务的编?/strong>
计数器大安知道了,比较单。我的计数器也同L单,有以下功能及特点Q提供四U计数器QL器、月计数器、周计数器及日计数器Q;考虑到Web
Service要服务于多种应用Q这个计数器q支持多个用P使用XML文g来记录数据?br />
记录文g名ؓQd:"counter.xml。注意,此文件在服务里是编码,如果修改名字Q请在服务程序中也进行相应的修改。文件内Ҏ式如下:
<?xml version="1.0"
encoding="UTF-8"?>
<counter>
<item>
<name>wallimn</name>
<password>123</password>
<dc>59</dc>
<wc>59</wc>
<mc>59</mc>
<tc>59</tc>
<rt>2007-4-16
16:01:29</rt>
</item>
<counter>
说是计数器服务,其实跟编写普通的JAVA应用没有什么两栗我的计数器代码比较单,我就不做q多的介l了Q把它脓在下面,源码中有量注释Q相信大家不看注释也看得懂。服务有只有一个接口:Counter(String
name, String
password),以后在客L拿来用就可以了。还多说一句,我和E序使用Cdom4j解析xml包,要调试的误行准备好jar包。部|方法请参照上文?
import java.io.File;
import java.io.FileWriter;
import
java.io.IOException;
import java.text.DateFormat;
import
java.text.ParseException;
import java.util.Calendar;
import
java.util.Date;
import java.util.Iterator;
import
org.dom4j.Document;
import org.dom4j.DocumentException;
import
org.dom4j.Element;
import org.dom4j.io.SAXReader;
import
org.dom4j.io.XMLWriter;
/**
*功能Q提供计数器服务的WebServiceQ可以ؓ多用h供服务?lt;br/>
*
@version : V1.0
* @author : 王力?Email: wallimn@sohu.com QQ: 54871876)
*
@date : 2007-4-16 下午04:32:45
*/
public class WsCounterByWallimn
{
//计数器文档,注意名字名\径?br />
private final String FileN =
"d:/counter.xml";
private final static DateFormat DATEFORMATER =
DateFormat.getDateTimeInstance();
//出错的情况下q回的?br />
private final static String
ERRORINFO="-1;-1;-1;-1";
public WsCounterByWallimn(){
}
/**
*功能Q打开计数器文?lt;br/>
*~码Q王力猛 旉Q?007-4-16
下午04:44:29<br/>
*/
private Document openDocument(){
Document
doc=null;
SAXReader reader = new SAXReader();
try {
File xmlfile = new
File(FileN);
doc = reader.read(xmlfile);
xmlfile=null;
}
catch
(DocumentException e) {
e.printStackTrace();
}
return
doc;
}
/**
*功能Q取指定名称计数器的详细信息Qƈ讄计数器加1?lt;br/>
*~码Q王力猛
旉Q?007-4-16 下午04:49:57<br/>
*/
private synchronized String
countertick( String name, String password){
Document doc =
openDocument();
Date currdt = new java.util.Date();
//mc:month
counter(月计数器); dc: day counter(日计数器);
//tc: total counter(总计数器); wc: week
counter(周计数器);
//rt: registe time登记旉
String
mc="-1",dc="-1",tc="-1",wc="-1",rt="-1";
Element root =
doc.getRootElement();
Element selitem=null,item=null;
for(Iterator it =
root.elementIterator("item"); it.hasNext();){
item =
(Element)it.next();
if(name.equals(item.element("name").getText())){
selitem=item;
String pwd =
item.elementText("password");
if(!password.equals(pwd)){
return
ERRORINFO;//密码不对Q直接返?br />
}
mc=item.element("mc").getText();
dc=item.element("dc").getText();
tc=item.element("tc").getText();
wc=item.element("wc").getText();
rt=item.element("rt").getText();
break;
}
}
//如果selitem为空Q说明没有个名字的计数器。则d一个?br />
if(selitem==null){
//System.out.println("没有扑ֈq个名字的计数器Q?+name);
rt=DATEFORMATER.format(currdt);
selitem
=
doc.getRootElement().addElement("item");
selitem.addElement("name").setText(name);
selitem.addElement("tc").setText("0");
selitem.addElement("mc").setText("0");
selitem.addElement("wc").setText("0");
selitem.addElement("dc").setText("0");
selitem.addElement("rt").setText(rt);
selitem.addElement("password").setText(password);
mc="0";
wc="0";
dc="0";
tc="0";
}
//处理计数器加一操作?br />
Calendar
currcr=Calendar.getInstance();
//L器L??br />
tc
=String.valueOf(Integer.parseInt(tc)+1);
selitem.element("tc").setText(tc);
Date
lastdt = null;
try {
lastdt = DATEFORMATER.parse(rt);
}
catch
(ParseException e) {
lastdt = new java.util.Date();
}
Calendar lastcr =
Calendar.getInstance();
lastcr.setTime(lastdt);
currcr.setTime(currdt);
//System.out.println("上次登记旉Q?+DATEFORMATER.format(lastdt));
//System.out.println("本次登记旉Q?+DATEFORMATER.format(currdt));
if(lastcr.get(Calendar.YEAR)==currcr.get(Calendar.YEAR)){
//月相同,月计数加1
if(lastcr.get(Calendar.MONTH)==currcr.get(Calendar.MONTH)){
mc
=
String.valueOf(Integer.parseInt(mc)+1);
}
else{
mc="1";
}
//日相同,日计数加1
if(lastcr.get(Calendar.DAY_OF_YEAR)==currcr.get(Calendar.DAY_OF_YEAR))
dc
= String.valueOf(Integer.parseInt(dc)+1);
else
dc =
"1";
if(lastcr.get(Calendar.WEEK_OF_YEAR)==currcr.get(Calendar.WEEK_OF_YEAR))
wc
= String.valueOf(Integer.parseInt(wc)+1);
else
wc = "1";
}
else{//q不一P则月计数器、周计数器日计数器肯定也不一栗?br />
mc="1"; dc="1"; wc="1";
}
selitem.element("mc").setText(mc);
selitem.element("wc").setText(wc);
selitem.element("dc").setText(dc);
//登记记录旉
selitem.element("rt").setText(DATEFORMATER.format(currdt));
try {
XMLWriter xw = new XMLWriter(new
FileWriter(FileN));
xw.write(doc);
xw.close();
}
catch (IOException
e) {
e.printStackTrace();
}
return
tc+";"+mc+";"+wc+";"+dc;
}
/**
*功能Q服务暴露的接口Q也是指定名称、密码,q回指定的计敎ͼq将计数器加1?lt;br/>
*~码Q王力猛
旉Q?007-4-17 上午10:05:22<br/>
*/
public String Counter(String name,
String password){
if(password==null || name==null)return ERRORINFO;
return
countertick(name, password);
}
}
四、客L~写
客户端是个页面,Z条理清晰Q我先写个调用Web
Service的类Q其内容如下Q?br />
package com.wallimn.WebService;//调试h意包?/p>
import org.apache.axis.client.Call;
import
org.apache.axis.client.Service;
public class CounterServiceClient {
private String counterarr[];
public boolean getCounter(String CounterName, String password) {
boolean
res = false;
try {
String endpoint =
"http://localhost:8080/axis/WsCounterByWallimn.jws";//此处注意Q请与你的开发环境匹?br />
Service
service = new Service();
Call call = (Call)
service.createCall();
call.setTargetEndpointAddress(new
java.net.URL(endpoint));
call.setOperationName("Counter");
//
填写你要调用的方法名U?br />
String counter = (String) call.invoke(new Object[] { CounterName,
password });
counterarr = counter.split(";");
res = (counterarr != null
&& counterarr.length == 4);
}
catch (Exception e)
{
}
return res;
}
public String getDc() {
return counterarr[3];
}
public String getMc() {
return counterarr[1];
}
public String getTc() {
return counterarr[0];
}
public String getWc() {
return counterarr[2];
}
}
到页?test.jsp)上就单了Q我也把它脓在下面:
<%@ page language="java"
import="com.wallimn.WebService.CounterServiceClient"
pageEncoding="GB18030"%>
<%
CounterServiceClient client = new
CounterServiceClient();
client.getCounter("hello","123");
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01
Transitional//EN">
<html>
<head>
<title>计数器测试页?lt;/title>
<meta
http-equiv="pragma" content="no-cache">
<meta
http-equiv="cache-control" content="no-cache">
<meta
http-equiv="expires" content="0">
<meta http-equiv="keywords"
content="wallimn,计数?WebService">
<meta http-equiv="description"
content="计数器用示?>
</head>
<body>
<h2
align="center">计数器详?lt;/h2>
<hr/>
总访问量:<%=client.getTc()%>
<br/>
今天讉K?<%=client.getDc()%>
<br/>
本周讉K?<%=client.getWc()%>
<br/>
本月讉K?<%=client.getMc()%> <br/>
<hr/>
<p>Ƣ迎交流<br/>博客Qhttp://blog.csdn.net/wallimn<br/>电邮Qwallimn@sohu.com</p>
</body>
</html>
五、结束语
x一个完整计数器的Web Service开发、用的E序的全部完成了。将上面的类、及面部vCQ意的一个上下文中,通过览器打开test.jspQ就可以看了l果了?br /> Ƣ迎讉K我的博客(http://blob.csdn.net/wallimn)留言或发邮g(wallimn@sohu.com)交流?Z发布在JavaEye的敬畏心情,本文又修改了一遍,与前两天发在blogjava和csdn的版本稍有不同?/p>
XFire 是全球众多牛人在与axispdҎ后一致投的选择。我比较ƣ赏的特性有Q?/p>
|上的文档与例子L不新Q请大家抛开所有的文档Q所有的Axis习惯Q单看这份代表XFire1.2.2最U做法的指南?/p>
注意XFire有了自己的ServletQ不再依赖Spring MVC的DispatchServletQ也pM大家不熟悉的Spring MVC URL MappingQ与Spring达致完美的整合?/p>
q里指定了\径ؓ/service/* Q即WebService的URL会被默认生成?a title="Visit page outside Confluence" rel="nofollow" linktext="http://www.springside.org.cn/bookstore/service/BookService" linktype="raw" mce_ >http://www.xxx.com/yyy/service/BookService
service/ServiceNamexml 代码
- <servlet>
- <servlet-name>xfireservlet-name>
- <servlet-class>org.codehaus.xfire.spring.XFireSpringServletservlet-class>
- servlet>
- <servlet-mapping>
- <servlet-name>xfireservlet-name>
- <url-pattern>/service/*url-pattern>
- servlet-mapping>
如果应用使用了HibernateQ用了OpenSessionInView FilterQ注意配|OSIV Filter Filter覆盖xfire servlet的\径,x例中?service/*.
从已有的BookManager.java中,抽取Z个窄接口Q仅暴露需要导ZؓWeb Service的方法。而BookManger.java是POJOQ不需要Q何WebService相关代码?/p>
H接口一斚w满了安全要求,不用整个BookManager所有方法导ZؓWeb ServiceQ另一斚wQXFire暂时也只支持Z接口的Proxy?/p>
public interface BookService {
List findBooksByCategory(String cateoryId);
}
XFire默认的Aegis Binding语法非常单,在SpringSide的例子里几乎一行配|都不用写,是我见过最单的binding定义Q大大优于其他以设计复杂为终极目标的Ҏ?/p>
对象的属性、函数的参数和返回值如果ؓint、String、Date{普通类型以及由普通类型组成的复杂对象都无需定义。我见到只有两种情况需要定义:
如果实在需要aegis配置Q?XFire以约定俗成代曉K|?CoC)Q所有Service和Entity Bean的binding文g要求命名为xxx.aegis.xmlQ而且要和原来的类sit together在同一目录里?/p>
其他语法详见Aegis 参考?/p>
Z节约配置代码Q先配置一个基cR注意导出Web服务的Bean不能lazy-initQ?/p>
每个Web服务的定义:parent为前面定义的基类QserviceClass 为Web Service的接口,serviceBean为Web Service的接口实现类?/p>
Web服务导出完毕Q用户可?a title="Visit page outside Confluence" rel="nofollow" href="http://localhost/service/BookService?WSDL">http://localhost/service/BookService?WSDL查看自动生成的WSDL?nbsp;
上半章完Q关于JSR181,Client API与测试部分请?a title="XFireGuide2" >XFire 生火指南(?
作者:江南白衣
本文来自SpringSide WIkiQ请留意Wiki上的最新版本?wiki?1.27更新)?/p>
请先阅读QXFire生火指南(?
JSR181式通过annotated POJO Q零配置文g的导出Web服务Q是BEA倡导的,JavaEE5里的正规方式Q?XFire作了良好的支持?/p>
但是QXFire关于JSR181方式的文档还不够清晰Q请完整阅读本节以避免其中的C陷阱?/p>
因ؓ配置都写在annotationQapplicationContext.xml文g的内Ҏ较固定。需要注意JSR181WebAnnotations与HandlerMapping不能lazy init.
不同于XFire传统模式Q窄接口不是必须的,只是考虑到client如果也用XFireӞ有个接口好生成Client而已?/p>
如果采用InterfaceQInterface担M要的配置工作?/p>
首先定义@WebServiceQ可定义自己的NameSpaceQ如果不定义采用NameSpace的默认生成算法?/p>
接口中的函数默认全部导出,不需要再用@WebMethod注释Q可以如下例般进行更q一步配|:
Manager不是Ua的POJOQ需要带上@WebService注释Q指明InterFace?/p>
陷阱一QXFire JSR181参考文?sup> 中在Interface中以@WebService(name="BookService")来定义ServiceNameQ这个做法看h也比较合理,但实际上需要在Manager中以@WebService(serviceName ="BookService") 来定义,比较古怪?/p>
参考文档中的例子,需要配|@WebMethod 指定需要导出的服务
陷阱二:和传l模式的client有一Ҏ大的区别Q第3个参数需要是实际的Managerc,而不是接口类Q?/p>
XFire的Clientq不强,一共有三种模式Q?/p>
Client与Server是同一个开发团队也好,Server端团队以jar形式提供开发包也好Q反正如果能拿到服务端的接口Class和Entitycdaegis 配置文g的话?/p>
传统模式Q?/p>
JSR181模式Q注意这里Server端开发组需要向Client提供BookService的实现类BookManagerQ而不止于接口c,有点危险Q?nbsp;
SpringSide 用泛型封装了一个XFireClientFactoryQ调用代码如下:
动态模式不需要服务端的classQ不q性能和复杂对象映等估计做得不会太好?/p>
Client client = new Client(new URL("http://www.webservicex.net/CurrencyConvertor.asmx?WSDL"));
Object[] results = client.invoke("ConversionRate", new Object[] {"BRL", "UGX"});
q才是Web Service Client的王道,可以讉KL~写下的Web ServiceQ将在下一个版本中演示?/p>
XFiire很重要的一个特性是提供了无d动Web容器也能q行单元试的能力?/p>
原理是利用XFire的JVM模式Q以xfire.local://BookService channel而不是http://localhost/service/BookService来访问服务?/p>
试的方式分两种Q?/p>
一U是U服务器角度Q不~写客户端代码,以SOAP XML形式发送请求,q回的也是SOAP XML字串Q直接对XMLq行试?/p>
一U是~写2.1 中Client代码来进行测试?/p>
前一U的试的隔d较高Q而后一U比较简ѝ?/p>
无论那种方式Q都使用Xfire?strong>AbstractXFireSpringTest基类Q实现createContext()回调函数?/p>
protected ApplicationContext createContext() {
return ClassPathXmlApplicationContext(new String[]{"classpath*:applicationContext*.xml"});
}
另外试基类q要完成一个很重要的工作就是要解决Hibernate的LazyLoad问题Q做到OpenSession In Test。因此,SpringSide专门装了一个XFireTestCase的基cR?/p>
下文直接用client代码调用findBooksByCategoryҎQ得到返回值后q行各种Assert判断?/p>
注意和普通client code的两处区别:servericeURL换成localQfactoryd入getXFire()作参数?/p>
~写一DSOAP XMLQ以L命名保存Q下文以"Java"作参敎ͼ调用findBooksByNameҎ?/p>
试代码调用前面的XMLQ返回XML Document对象Q再用基cL供的一些AssertҎ查结果:
Z实现Web Services的^台无x和实现讉K独立性,软g行业需要遵循一些作为标准的技术。其中一些包?
---XML:在Web Services环境中各层之间进行传递的默认数据格式?/a>
下面的高U层ơ图表,ZWWW协会发布?#8220;Web Services Architecture”(Web Services架构)文档Q显CZq些技术在实际的工作环境中是如何发挥作?
q个程图显CZWeb Services中的核心技术是如何工作的?/p>
q里QProvider是提供服务的应用E序lgQRequester是用服务的客户端程序。很多其他技术也会参与到交互中,但是q个囑֏昄了在Web Services环境中必需的核心技术组件?/p>
XFire是一个免费的开源SOAP框架Q它不仅可以极大方便地实现这样一个环境,q且可以提供许多Web Services规范中高U特征,q些特征在多数的商业或者开源工具都没有提供。你要恰当的理解q些单词:great ease and simplicity(非常L和简?。你会看到使用XFire创徏Web Services是多么的单?/p>
package com.mybank.xfire.example; import java.text.NumberFormat; /** XFire WebServices sample implementation class. |
因ؓ使用接口的设计是一个好的实践,所以我们的JavacM实现了一个称为IBankingService的接口。代码十分简?
package com.mybank.xfire.example; public interface IBankingService { public String transferFunds( |
在实际实CQ这样一个方法可能包括各U类型的复杂调用、查询和处理操作。但是我们的CZ代码已经最化了,以至于我们可以集中精力在主要目标?把这个方法发布ؓWeb Services?/p>
你可以看到BankingService是一个普通的Javac,没有M代码告诉我们它将会在Web Services中用。好的,q里我们不需要增加Q何东ѝ我们所有的工作都在部v描述W里完成?/p>
Web应用的部|描q符
在Java中,Web应用E序通常需要至一个部|描q符(叫做web.xml)对其q行配置。XFire本n是一个基于servlet的应? E序。因此,我们需要增加必要的引用到描q符文g中。然后我们还必须配置要创徏的Web Services。我们用一个称为services.xml的新文g来完成这件事?/p>
web.xml
首先Q修改web.xml。我们需要增加下面的XFire servlet相关的条?
<servlet>
<servlet-name>XFireServlet</servlet-name> <display-name>XFire Servlet</display-name> <servlet-class>org.codehaus.xfire.transport.http.XfireConfigurableServlet </servlet-class> </servlet> <servlet-mapping>
<servlet-name>XFireServlet</servlet-name> <url-pattern>/servlet/XFireServlet/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>XFireServlet</servlet-name> <url-pattern>/services/*</url-pattern> </servlet-mapping> |
<beans xmlns="http://xfire.codehaus.org/config/1.0"> <service> <name>Banking</name> <namespace>mybank</namespace> <serviceClass>com.mybank.xfire.example.IBankingService</serviceClass> <implementationClass>com.mybank.xfire.example.BankingService</implementationClass> </service> </beans> |
<implementationClass>保存了实现方法的Javacd。这是一个可选元素。如果上一个元?lt;serviceClass>包含了一个接口,那么相应的实现类必须在这里指定?/font>
是q样。我们的Web Services配置完成?/font>?/a>
我们如何知道Web Service正在工作?
Z了解Web Service是否正在工作Q我们需要测试。首先,我们试来看WSDL是否可用。我们在览器中输入URL。哪个URL?因ؓ我们的应用程序的war? 件是websvc.warQƈ且在services.xml中给出的服务名是BankingQWSDL的URL应该?http: //localhost:8080/websvc/services/Banking?wsdl?/p>
h?URL的第一部分Q例如,http://localhost:8080Q可能会Ҏ你的应用服务器不同而不同。无论怎样Q当你输入URL后,会看到一个XML文档Q它的根元素是。这个文档叫做服务的WSDL。如果你看到了,q就是你的应用作为Web Service已经可用的第一个证明?/p>
但是q个试是不够的。可能会发生q种情况Q可以看到WSDLQ但是从客户端程序可能会讉K不到服务。因此ؓ了核实服务是否可以访问了Q我们必M用一个客Lq行服务的实际调用来q行一个真正的试?/p>
开发一个客L
你可以用Q何的SOAP工具创徏客户端,例如Q?Net或者Apache AxisQ有很多U方?使用从WSDL产生的stubsQ用动态代理,{等。在例子中,我们使用一个动态代理,以一个简单的Servlet形式Q叫? WsClient.java。ؓ了保持代码两最,所有在屏幕昄的元素都攑֜了doGet()Ҏ中。对Web Service的实际调用由callWebService()Ҏ完成Q它相当地简单。和下面的类?
/* Call the Web service //Return the response |
q个代码是如何工作的?我来解释一?首先Q我们创Z个服务模型,它包含服务的说明——换句话_是服务的元数据。我们用XFire的ObjectServiceFactory从IBankingService.class接口创徏q个模型?/p>
接着QؓXFire获得一个代理工厂对象,它包含了常规的代码,也相当地单和易懂。这一步中没有M特定应用的东ѝ从q个proxyFactoryQ用服务模型和服务端点URL(用来获得WSDL)Q我们可以得C个服务的本地代理?/p>
是它了。这个代理就是实际的客户端。现在,我们可以调用它的transferFunds()Ҏ来得到我们需要的Web Service?/a>
http://localhost:8080/websvc/ws?/a>
q个Servlet使用默认参数来调用Web Service和显C接收到的响应。页面的最后两行应该读?
现在你可以确定Web Service已经发布q且在运行中了?/a>
http://localhost:8080/websvc/ws?from=11-2345&to=77-9876&amt=250.00&cur=EUR?/a>
q个清单ȝ了将一个JavaҎ发布为Web Service所必须的步?
XFire的用可能比较简单,但是在特性和功能性上Q它却占据着领导者的位置。下面是它的高Ҏ?
String serviceUrl = "http://localhost:8080/websvc/services/Banking";
XFire支持JRS181方式的标注服务开发,q样开发的时候只需要编写一个普通的Javac,然后加上标注信息后,加入services.xml中即可。我们仍然在W?/span>2节所开发的HelloWorldService目中进行修攏V下面是创徏?/span>Web服务c?/span>echo.Jsr181EchoServiceQ?/span>
package echo; import javax.jws.WebMethod; import javax.jws.WebParam; import javax.jws.WebResult; import javax.jws.WebService; @WebService(name = "EchoService", serviceName="EchoServiceTest", targetNamespace = "http://www.openuri.org/2004/04/HelloWorld") publicclass Jsr181EchoService { @WebMethod(operationName = "echoString", action = "urn:EchoString") @WebResult(name = "echoResult") publicString echo(@WebParam(name = "echoParam", header = true) String input) { return input; } } |
q个代码中有很多标注Q绝大部分都可以在最后生成的WSDL文档中找到对应倹{?/span>
@WebServiceq个标注攄?/span> Java cM前,注明q个cȝ部分Ҏ可以被发布ؓ Web 服务Q还记得上一章提到的标注嘛?q个标注最l被XFired后进行分析后会进一步处理成Web服务Q。它的属性用于设|?/span> Web 服务被发布时的配|信息,常用的属性包括:
nameQ可选)Q?/span>Web 服务的名字,WSDL?/span> wsdl:portType元素?/span> name属性和它保持一_默认?/span> JavacL者接口的名字Q也可以q行自定义,例如本例中的EchoService?/span>
serviceNameQ可选)Q?/span>Web 服务的服务名Q?/span>WSDL ?/span> wsdl:service元素?/span> name属性和它保持一_默认?/span>Javacȝ名字Q?/span>Jsr181EchoServiceQ,不过如果讄?/span>name属性,则名字改?/span>name属性的取倹{?/span>
targetNamespaceQ可选)Q?/span> WSDL文g所使用?/span> namespaceQ该 Web 服务中所产生的其?/span> XML文档同样采用q个作ؓnamespaceQ一般取gؓWeb服务所在网站的名字Q不q看hL取值ƈ无出错之处?/span>
@WebMethodQ可选) 标注攑֜需要发布成 Web 服务的方法之前,有一些属性可以设|。例?/span>openrationName指明?/span>SOAP调用时所看到的方法名?/span>echoStringQ而不是类中的Ҏ?/span>echoQ?/span>action则定义了操作的类型。一个类里面可以定义多个@WebMethod?/span>
@WebResultQ可选)标注定义了返回|SOAP Response EnvolopeQ中?/span>name(名字)?/span>echoResult?/span>
@WebParamQ可选)则定义了哪些参数可以作ؓWeb服务中的q程可见的参数被调用Q?/span>name讄了其属性?/span>
乍看之下Q这?/span>Web服务中所用的标注有点多,实际上,q些标注可以不加M属性,例如只写?/span>@WebServiceQ?/span>@WebMethod卛_Q甚至于整个cd需要一?/span>@WebService标注卛_Q此时代码如下所C:
package echo; import javax.jws.WebService; @WebService publicclass Jsr181EchoService { public String echo( String input) { return input; } } |
。此时最后所生成?/span>Web服务中,所有的操作名,Ҏ名和参数名都和此?/span>JavacM的名UC致。在q种情况下,?/span>Web服务的访问地址应ؓQ?/span>
http://localhost:8080/HelloWorldService/services/Jsr181EchoService?wsdl ?/span>
最后一步,乃是?/span>XFire中配|ƈ发布此服务了Q在services.xml中加入的Web服务配置格式如下Q?/span>
<service> <!-- 如果配置文g中配|了额外?/span>name属性,那么最l的Web Service 名字会以此处ؓ准,卻I ServiceName?wsdlQ同Lnamespace的取g会覆?/span>JavacM的标注的倹{?/span> <name>ServiceName</name> <namespace>http://www.un.gov/HelloEcho</namespace> --> <serviceClass>echo.Jsr181EchoService</serviceClass> <serviceFactory> org.codehaus.xfire.annotations.AnnotationServiceFactory </serviceFactory> </service> |
Q此配置相当的简单,需要注意的?/span>serviceClass是我们写有Web服务标注?/span>Javac,?/span>serviceFactory则必LAnnotationServiceFactoryQ标注服务工厂)Q否则此Web服务无法正常发布?/span>
好了Q现在按?/span>15.2.4一节的内容发布目q运行后Q即可在览器中键入下面地址q行试了:
http://localhost:8080/HelloWorldService/services/EchoServiceTest?wsdl ?/span>
EchoServiceTest的名字是因ؓ在类中标注了@WebService?/span>serviceName属性。之后就可以?/span>Web Service Explorer中对它进行测试,或者是生成客户端代码?/span>
从这里看到这U开发方式相对也单的多,而且您将在下一节看到如果是Java EE 5的容器,开发过E将更加的简单,而且不需?/span>XFirecd及其配置文g?/span>