maxOccurs="unbounded">
SystemError和BusinessError类型都定义errorCode元素。
此元素包含一个字母数字代码,唯一地标识服务可以返回的每个错误类型。
服务将字母数字代码与描述错误的文本信息一起返回非常重要,因为客户机应用程序(服务使用者)可能会希望以编程方式检查此代码,并以不同的方式定向后续调用(根据错误代码所指示的错误类型)。
最佳实践:
错误消息应该包含错误代码,以允许服务使用者应用程序(客户机)以编程方式处理错误。
应该将错误代码作为WSDL服务定义不可或缺的部分发布,以便于服务使用者应用程序理解所报告的错误的含义。
清单2显示了用于将错误代码传递到服务使用者应用程序的简单类型的定义。
两个简单类型BusinessErrorCodeType和SystemErrorCodeType都通过限制(restriction)派生自xsd:
string类型。
限制中的枚举定义可以为业务级别错误和系统级别错误返回的错误代码。
XSD中嵌入的注释可为服务使用者(客户机)应用程序开发人员描述错误代码的含义。
清单2.定义
--Invalidsymbolstringformat-->
--Symbolnotfound-->
--Authenticationerror-->
--Databaseconnectionnotavailable-->
此示例将业务级别的错误和系统级别的错误归入不同的类型,以明确地对二者进行区分,而且可供服务使用者应用程序方便地以编程方式处理错误代码(如果必要)。
在很多情况下,只需定义单个简单类型,就足以包含所有描述业务级别和系统级别的错误语义的错误代码。
不过,将业务和系统错误代码分组到独立的编号段的做法(例如,业务级别的错误从00010到00100,而系统级别的错误从001000到010000)非常明智,可便于对这些代码进行编程处理。
对于即使在例外路径上性能也非常关键的情况,您可能会希望将错误代码字段设置为int或short类型,因为数字型字段比字符串字段处理起来更快。
最佳实践:
应该将系统和业务错误的数字值分组为不同的段(请参见下载),以便服务使用者应用程序代码以编程方式进行检查。
SystemErrorType类型包含可选元素,可用于捕获关于导致问题的错误的信息。
例如,StockQuote服务可能在尝试与后端数据库进行通信时捕获java.sql.SQLException。
为了对此进行响应,我们的服务将构造并引发SystemErrorType。
SytemErrorType应该包含原始异常的名称,还可以选择性地提供导致问题的java.sql.SQLException异常中的额外消息堆栈跟踪信息。
在大多数情况下,如果服务使用者和服务提供者应用程序属于相同的组织,服务开发人员可能会选择不向服务使用者应用程序报告原始错误。
这是一个非常好的做法,可促进问题确定和解决。
在操作响应中返回错误
使用WSDL错误的另一个方法是将错误信息作为常规响应消息的一部分返回。
应用程序开发人员趋向于采用此方法,特别在涉及到使用Web服务进行批处理时。
这种情况下的批处理应用到的操作的输入消息包含多个不同的输入,而响应消息可能包含多个不同的结果(每个均与某个输入对应)。
例如,StockQuote服务的输入消息可能包含多个股票代码,而响应消息也将包含多个结果。
此方法具有一系列优缺点。
(本系列的后续文章将更详细地讨论与批处理相关的问题。
)
在非错误响应中返回错误代码的主要好处在于,此方法允许服务使用者应用程序(客户机)按照与处理非错误响应相同的方式处理错误信息。
图2显示了如何采用此方式返回错误信息。
这样,客户机应用程序就可以获取Web服务返回的响应Java对象,并直接将其传递到表示层(通常为JavaServerPages),以向最终用户显示。
这就避免了创建用于将服务使用者应用程序的表示层与Web服务层分离的转换对象的过程中涉及到的额外编程工作。
图2.作为响应消息的一部分返回错误信息的单向操作与请求-响应操作
此方法的主要缺点有:
∙客户机应用程序必需访问资源,检查响应中提供的结果元素是否为空,分析并返回systemError和businessError值,并恰当地做出反应。
业务逻辑与大量的技术查询和条件语句混杂在一起。
使用WSBPEL编写的业务流程特别容易受到负面影响。
Web服务业务流程执行语言(WebServicesBusinessProcessExecutionLanguage,WSBPEL)提供了错误处理程序构造,能够通过图形构建程序工具将其从主要业务逻辑活动序列分离出来。
如果WSBPEL流必须持续对响应结构进行自检,并评估结果和错误字段的值,那么所得到的图形将很难提交给业务用户并向其进行说明。
∙服务使用者应用程序(客户机)代码的用户界面部分与服务接口紧密耦合。
以后对服务接口的更改可能导致有必要重新编写用户界面。
最佳实践:
对于不涉及批处理的请求-响应操作,请使用WSDL错误从服务接口将错误信息返回到服务使用者应用程序,而不要在响应消息和状态代码中返回错误信息。
∙
∙服务使用者应用程序运行所在的应用程序服务器将不会注意到Web服务所导致的问题。
返回结果的所有响应看起来都像没有发生错误的调用,而且跟踪文件或system.out日志中将不会显示有关问题的信息,因此会对问题确定带来障碍。
由于存在这些缺点,因此从请求-响应操作返回错误和消息的最佳做法是依赖于WSDL错误构造,并使用前面描述的方法返回错误。
在异步拓扑中返回错误
WSDL规范(请参见参考资料)并不允许为单向操作定义错误。
不过,遵照异步模型构建的应用程序的确需要访问错误信息的功能。
可以采用以下三种方式之一设计异步调用的结构:
∙无响应的单向异步调用
发布-订阅交互就是此方式的一个示例。
当应用程序实现没有响应的单向异步调用时,由于WSDL规范不允许为所使用的单向操作定义错误,因此没有办法向服务使用者应用程序提供错误反馈。
服务使用者应用程序也对稍后获取响应没有兴趣,因此没有办法使用响应消息向客户机提供错误信息。
在此情况下,您必须非常细心地处理基础设施配置,以确保请求消息不会在发往预期目的地的过程中丢失。
最佳实践:
实现异步拓扑时,使用响应消息返回错误。
∙稍后发出响应请求的异步调用
涉及到执行两个请求-响应操作(虽然大部分处理工作都是由目标服务以异步方式执行)。
例如:
1.服务使用者应用程序调用submitStockQuoteRequest()操作。
此操作向服务使用者应用程序返回事务确认编号。
2.stockQuoteService实现执行股票值的查询操作。
3.服务使用者应用程序稍后调用getStockQuoteResponse()操作,并传入submitStockQuoteRequest()返回的事务编号。
此操作返回的响应操作包含股票值结果和错误信息(如果有)。
图3显示了在这种情况下应该如何设计操作签名的结构。
请求操作可能会导致系统错误。
这些错误仅仅传递与输入消息交互及异步处理初始化相关的错误信息。
用于检索响应消息的操作应该按照上面所述的常规请求-响应操作的方式引发系统和业务错误。
图3.StockQuoteAsynch端口类型的结构
∙使用回调返回请求的异步调用
服务使用者应用程序提供相关信息,以允许服务将响应消息发送回客户机(服务使用者)应用程序定义的接口。
如果在异步处理阶段发生业务错误,服务实现将在查询响应消息时引发业务错误。
最佳实践:
当使用请求-响应操作进行异步处理时,在请求操作上仅定义系统错误,而在用于检索响应消息的操作上同时定义系统级别和业务级别的错误。
处理客户机应用程序中的错误
清单3显示了一个示例,说明了客户机应用程序将如何处理StockQuote所引发的业务和系统错误。
JavaAPIforXML-basedRPC(JAX-RPC)和JavaAPIforXMLWebServices(JAX-WS)规范说明了WSDL和XSD所描述的类型如何转换为客户机所使用的Java对象。
由于我们定义了不同的复杂类型来表示这些错误,因此RationalApplicationDeveloperWeb服务工具会生成不同的JavaBean来表示它们。
这些Bean派生自JavaException类,可以在不同的catch块中进行处理。
最佳实践:
调用Web服务时,捕获所有已声明的错误,并在独立的catch块中处理系统和业务错误。
客户机应用程序调用Web服务时,必须处理生成的代理的方法签名中定义的异常。
除了这些异常外,客户机堆栈还可能会引发与序列化和通信相关的其他错误。
通常,除了处理代理方法签名中定义的异常外,最好也对基础java.lang.Throwable进行处理。
这可确保客户机应用程序代码足够强壮,能够处理客户机为了响应各种情况(如格式存在问题的响应消息——序列化类型错误)而引发的非预期异常。
清单3.请求-响应处理的客户端错误处理示例
publicclassSQFacade{
privateintretryCount=0;
privateStockQuoteProxysqProxy=newStockQuoteProxy();
publicStringgetQuote(Stringsymbol)throwsUIError
{
Stringresult=null;
UIErrorerror=newUIError();
try{
result=sqProxy.getQuote(symbol);
}catch(BusinessErrorTypee){
//Checkthecodeandreturnappropriateactiontouser
BusinessErrorCodeTypeerrorCode=e.getErrorCode();
if(errorCode!
=null)
{
if(errorCode.getValue().equals(BusinessErrorCodeType._SQ00010))
{
error.setMessage("Invalidinputformat!
");
error.setAction("Pleasere-enterthesymbolvaluecorrectly.");
}
elseif(errorCode.getValue().equals(BusinessErrorCodeType._SQ00020))
{
error.setMessage("Stocksymbolnotfound!
");
error.setAction("Pleaseenteradifferentstocksymbol.");
}
else//Justincase!
{
error.setMessage("Usererror!
");
error.setAction("Pleasecontactsiteadministrator.");
}
}
else//Justincase!
{
error.setMessage("Usererror!
");
error.setAction("Pleasecontactsiteadministrator.");
}
throwerror;
}
catch(SystemErrorTypee)
{
//Checkthecodeandreturnappropriateactiontouser
SystemErrorCodeTypeerrorCode=e.getErrorCode();
if(errorCode!
=null)
{
if(errorCode.getValue().equals(SystemErrorCodeType._SQ001001))
{
error.setMessage("Authenticationproblem!
");
error.setAction("Pleasepleasecontactthesiteadministrator.");
}
elseif(errorCode.getValue().equals(SystemErrorCodeType._SQ001002))
{
//Retry3timestoseeifdbconnectionproblemclearsup
if(retryCount<3)
{
retryCount++;
this.getQuote(symbol);
}
retryCount=0;
error.setMessage("Dataaccessproblems!
");
error.setAction("Pleasetryagainatalatertime.");
}
else//Justincase!
{
error.setMes