5.处理端点异常
事情不会一帆风顺。传输的消息有可能不能转化成Java对象或消息根本就不是合法的XML,因此服务端点就有可能抛出一个异常——我们应该怎么处理它呢?
如果处理消息时出现异常,我们需要向客户返回一个SOAP错误。不过,SOAP并不识别Java异常,而基于SOAP的web service通常都使用SOAP错误来表明失败。所以,我们需要将由web service或Spring-WS抛出的java异常转化成SOAP错误。
Spring-WS提供了SoapFaultMappingExceptionResolver,它负责处理在消息处理过程中出现的任何未捕获异常,并产生一个对应的SOAP错误返回给客户。在服务端,我们需要如下配置它:
<bean id="endpointExceptionResolver"
class="org.springframework.ws.soap.server.endpoint.
SoapFaultMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<prop key="org.springframework.oxm.
UnmarshallingFailureException">
SENDER, Invaild message received</prop>
<prop key="org.springframework.oxm.
ValidationFailureException">
SENDER, Invaild message received</prop>
</props>
</property>
<property name="defaultFault" value="RECEIVER, Server error" />
</bean>
exceptionMappings属性配置了一个或多个SOAP错误与java异常的映射规则。每个<prop>的值都是一个Java异常,它都需要被转换成SOAP错误。每个<prop>值由两部分组成,第一部分表明错误类型;第二部分是描述错误的字符串。
SOAP错误有两种类型:sender和receiver。Sender错误一般表示客户端的错误。Receiver错误表明是web service从客户端接收消息,但在处理消息时出错。
举例来说,如果一个服务接收到一个不能被转换成java对象的XML消息,那么marshaler就会抛出org.springframework.oxm.UnmarshallingFailureExce-
ption异常。因此发送方(sender)创建的该XML,所以这是一个sender错误。因此我们将该消息设置为“无效消息”。ValidateFailureException异常与之类似。
任何没有显式地在exceptionMappings属性中配置映射的异常都将按defaultFault属性中定义的映射规则处理。上面的代码中,我们假定如果抛出的异常不匹配任何映射异常,那么我们视之为一个接收方(receiver)错误,所以,我们配置了接收方错误和错误信息:“server error”。
6. 提供WSDL文件
最后我们来看poker hand evaluation例子的WSDL文件的写法。我们已经创建了contract的数据部分作为XSD。那么现在我们选用EvaluateHandRequest和EvaluateHandResponse作为组成web service消息的XML元素。选用这两个名字是有目的的,因为我们可以利用Spring-WS提供的惯例优先原则来自动地创建WSDL。
首先,我们需要配置Spring-WS的DynamicWsdl11Definition,它会与MessageDispatcherServlet一同从XML Schema创建WSDL。这很容易实现,因为我们定义过XSD,下面展示如何配置DynamicWsdl11Definition:
<bean id="poker" class="org.springframework.ws.wsdl.wsdl11.
DynamicWsdl11Definition">
<property name="builder">
<bean class="org.springframework.ws.wsdl.wsdl11.builder.
XsdBasedSoap11Wsdl4jDefinitionBuilder" >
<property name="schema" value="/PokerTypes.xsd" />
<property name="portTypeName" value="Poker" />
<property name="locationUri"
value="http://localhost:8080/Poker-WS/services" />
</bean>
</property>
</bean>
DynamicWsdl11Definition读取一个XSD(这里是PokerTypes.xsd),遍历schema文件来查找任何以Request和Response名字结尾的元素定义,因为它认定Request和Response后缀分别表明被发送到web service和从web service操作发出的消息,并在WSDL中创建一个相应的<wsdl:operation>元素。
本例中,DynamicWsdl11Definition将会读取PokerTypes.xsd文件,并将EvaluateHandRequest和EvaluateHandResponse元素分别作为EvaluateHand操作的输入和输出消息。因此,配置如下:
<wsdl:portType name="Poker">
<wsdl:operation name="EvaluateHand">
<wsdl:input message="schema:EvaluateHandRequest"
name="EvaluateHandRequest">
</wsdl:input>
<wsdl:output message="schema:EvaluateHandResponse"
name="EvaluateHandResponse">
</wsdl:output>
</wsdl:operation>
</wsdl:portType>
值得注意的是,<wsdl:portType>由绑定在DynamicWsdl11Definition的portTypeName属性中的值来命名。DynamicWsdl11Definition的最后一个属性是locationUri,它表明服务的地址。localhost表明它是运行在本机,/services表明它将匹配<servlet-mapping>中的配置。除此之外,我们还需要在web.xml中添加一个新的<servlet-mapping>,这样MessageDispatcherServlet才能提供WSDL。配置如下:
<servlet-mapping>
<servlet-name>poker</servlet-name>
<url-pattern>*.wsdl</url-pattern>
</servlet-mapping>
到此为止,我们已经配置过了MessageDispatcherServlet来自动生成WSDL文件。但现在的问题是这个WSDL文件在哪里呢?
自动创建的WSDL在http://localhost:8080/Poker-WS/poker.wsdl处。因为MessageDispatcherServlet会被映射到*.wsdl,所以它会为每个匹配的请求尝试去创建WSDL。但是它怎么知道为poker service在poker.wsdl上创建WSDL呢?答案就在于MessageDispatcherServlet遵循的惯例。值得注意的是,我们前面声明过DynamicWsdl11Definition bean来获取poker的ID值。当MessageDispatcherServlet接收到一个请求时,它会在Spring context中寻找命名为poker的WSDL bean。本例中就是DynamicWsdl11Definition。
使用预定义WSDL
DynamicWsdl11Definition大多数情况下都不需要你手动编写WSDL,但是在某些情况下你还是需要定制你自己的WSDL。本例中你需要手动创建WSDL并把它绑定到Spring中:
<bean id="poker"
class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
<property name="wsdl" value="/PokerService.wsdl" />
</bean>
SimpleWsdl11Definition并不会创建WSDL,它只会通过wsdl属性提供WSDL。
预定义WSDL的唯一问题在于它是以静态方式定义的,这对于指定服务地址的WSDL就会是一个问题。例如,考虑下么的WSDL代码:
<wsdl:service name="PokerService">
<wsdl:port binding="tns:PokerBinding" name="PokerPort">
<wsdlsoap:address
location="http://localhost:8080/Poker-WS/services" />
</wsdl:port>
</wsdl:service>
这里由http://localhost:8080/Poker-WS/services定义了服务的地址。如果你需要在另一个服务器上部署,那么这个地址就有可能需要变更。你可以每次手动地修改这个值,但是这个过程容易出错。
但是MessageDispatcherServlet知道它被部署的地方,而且它也知道请求的URL。因此,你可以让MessageDispatcherServlet重写来完成这个地址值变更过程。你需要的就是设置名为transformWsdlLocations的<init-param>为true,例如:
<servlet>
<servlet-name>poker</servlet-name>
<servlet-class>org.springframework.ws.transport.http.
MessageDispatcherServlet</servlet-class>
<init-param>
<param-name>transformWsdlLocations</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
当transformWsdlLocation被设置成true时,MessageDispatcherServlet将重写SimpleWsdl11Definition提供的WSDL来匹配请求URL。
7. 部署服务
我们已经配置了contract,编写了端点,并且所有的Spring-WS bean都已经准备就绪了。现在我们需要把这些打包成一个WAR包并将其部署。
使用Spring-WS来构建一个web service只是它的一个方面。Spring-WS还提供了一个客户API。该API基于与服务器端相同的消息驱动机制。