II. 参考

本参考文档的这一部分详细介绍了构成 Spring Web Services 的各个组件。 其中包括 一章,讨论了客户端和服务端 WS 共有的部分,一章专门介绍 编写服务端 Web 服务 的具体细节,一章关于在 客户端 使用 Web 服务,以及一章关于使用 WS-Security

4. 共享组件

本章探讨了客户端和服务器端 Spring-WS 开发之间共享的组件。这些接口和类代表了 Spring-WS 的构建块,因此即使您不直接使用它们,也需要理解它们的作用。spring-doc.cadn.net.cn

4.1. 空安全性

Spring-WS 使用 JSpecify 注解来声明其 API 的可空性。 要了解更多关于 JSpecify 的信息,建议阅读其 用户指南spring-doc.cadn.net.cn

声明 API 可空性的主要目的是防止在运行时抛出 NullPointerException。 这是通过使用 Java 和 Kotlin 都支持的构建时检查来实现的。 在 Java 中执行这些检查需要一些工具支持,例如 NullAway 或者支持 JSpecify 注解的 IDE,比如 IntelliJ IDEA。 Kotlin 则会自动进行这些检查,它将 JSpecify 注解转换为 Kotlin 的空安全机制。spring-doc.cadn.net.cn

要了解更多关于 Spring 的空安全性,请参阅 Spring 框架参考文档spring-doc.cadn.net.cn

4.2. Web服务消息

本节描述了 Spring-WS 使用的消息和消息工厂。spring-doc.cadn.net.cn

4.2.1. WebServiceMessage

Spring-WS 的核心接口之一是 WebServiceMessage。该接口表示与协议无关的 XML 消息。此接口包含一些方法,提供对消息负载的访问,形式为 javax.xml.transform.Sourcejavax.xml.transform.ResultSourceResult 是标记接口,代表对 XML 输入和输出的抽象。具体实现包装了各种 XML 表示形式,如下表所示:spring-doc.cadn.net.cn

源或结果实现 包装的 XML 表示

javax.xml.transform.dom.DOMSourcespring-doc.cadn.net.cn

org.w3c.dom.Nodespring-doc.cadn.net.cn

javax.xml.transform.dom.DOMResultspring-doc.cadn.net.cn

org.w3c.dom.Nodespring-doc.cadn.net.cn

javax.xml.transform.sax.SAXSourcespring-doc.cadn.net.cn

org.xml.sax.InputSourceorg.xml.sax.XMLReaderspring-doc.cadn.net.cn

javax.xml.transform.sax.SAXResultspring-doc.cadn.net.cn

org.xml.sax.ContentHandlerspring-doc.cadn.net.cn

javax.xml.transform.stream.StreamSourcespring-doc.cadn.net.cn

java.io.File, java.io.InputStream, or java.io.Readerspring-doc.cadn.net.cn

javax.xml.transform.stream.StreamResultspring-doc.cadn.net.cn

java.io.File, java.io.OutputStream, or java.io.Writerspring-doc.cadn.net.cn

除了从payload读取和写入之外,Web服务消息还可以将自身写入输出流。spring-doc.cadn.net.cn

4.2.2. SoapMessage

SoapMessageWebServiceMessage 的子类。 它包含 SOAP 特定的方法,例如获取 SOAP 头、SOAP 错误等。 通常,您的代码不应依赖于 SoapMessage,因为 SOAP 主体的内容(消息的有效负载)可以通过在 WebServiceMessage 中使用 getPayloadSource()getPayloadResult() 来获取。 只有在需要执行特定于 SOAP 的操作时(例如添加头、获取附件等),才需要将 WebServiceMessage 转换为 SoapMessagespring-doc.cadn.net.cn

4.2.3. 消息工厂

Concrete message implementations are created by a WebServiceMessageFactory。 This factory can create an empty message or read a message from an input stream。 There are two concrete implementations of WebServiceMessageFactory。 One is based on SAAJ, the SOAP with Attachments API for Java。 The other is based on Axis 2’s AXIOM (AXis Object Model)。spring-doc.cadn.net.cn

SaajSoapMessageFactory

SaajSoapMessageFactory 使用 Java 的 SOAP with Attachments API (SAAJ) 来创建 SoapMessage 实现。 SAAJ 是 J2EE 1.4 的一部分,因此它应该在大多数现代应用服务器下得到支持。 以下是常见应用服务器提供的 SAAJ 版本概述:spring-doc.cadn.net.cn

应用服务器 SAAJ 版本

BEA WebLogic 8spring-doc.cadn.net.cn

1.1spring-doc.cadn.net.cn

BEA WebLogic 9spring-doc.cadn.net.cn

1.1/1.21spring-doc.cadn.net.cn

IBM WebSphere 6spring-doc.cadn.net.cn

1.2spring-doc.cadn.net.cn

SUN Glassfish 1spring-doc.cadn.net.cn

1.3spring-doc.cadn.net.cn

1Weblogic 9 在 SAAJ 1.2 实现中有一个已知的错误:它实现了所有的 1.2 接口,但在调用时会抛出 UnsupportedOperationException。 Spring-WS 提供了一个解决方案:在 WebLogic 9 上运行时,它使用 SAAJ 1.1。spring-doc.cadn.net.cn

此外,Java SE 6 包含 SAAJ 1.3。 您可以按如下方式连接 SaajSoapMessageFactoryspring-doc.cadn.net.cn

<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />

SAAJ 基于 DOM,即文档对象模型。 这意味着所有的 SOAP 消息都存储在内存中。 对于较大的 SOAP 消息,这可能性能不佳。 在这种情况下,AxiomSoapMessageFactory 可能更适用。spring-doc.cadn.net.cn

AxiomSoapMessageFactory

AxiomSoapMessageFactory 使用 AXis 2 对象模型 (AXIOM) 来创建 SoapMessage 实现。 AXIOM 基于 StAX,即 XML 的流式 API。 StAX 提供了一种基于拉取的机制来读取 XML 消息,这对于较大的消息可能更高效。spring-doc.cadn.net.cn

为了提高在 AxiomSoapMessageFactory 上的读取性能,您可以将 payloadCaching 属性设置为 false(默认值为 true)。这样做会导致 SOAP 主体的内容直接从套接字流中读取。启用此设置后,负载只能被读取一次。这意味着您必须确保消息的任何预处理(日志记录或其他操作)不会消耗它。spring-doc.cadn.net.cn

你可以如下使用 AxiomSoapMessageFactoryspring-doc.cadn.net.cn

<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory">
    <property name="payloadCaching" value="true"/>
</bean>

除了有效负载缓存之外,AXIOM 还支持完全流式传输的消息,如 StreamingWebServiceMessage 中所定义。 这意味着您可以直接在响应消息上设置有效负载,而不是将其写入 DOM 树或缓冲区。spring-doc.cadn.net.cn

当处理方法返回一个受JAXB2支持的对象时,AXIOM会使用完整的流式传输。 它会自动将此编组对象设置到响应消息中,并在响应发送出去时将其写入到传出的套接字流中。spring-doc.cadn.net.cn

有关完整流的更多信息,请参见 StreamingWebServiceMessageStreamingPayloadspring-doc.cadn.net.cn

SOAP 1.1 或 1.2

Both the SaajSoapMessageFactory and the AxiomSoapMessageFactory have a soapVersion property, where you can inject a SoapVersion constant. By default, the version is 1.1, but you can set it to 1.2:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd">

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory">
        <property name="soapVersion">
            <util:constant static-field="org.springframework.ws.soap.SoapVersion.SOAP_12"/>
        </property>
    </bean>

</beans>

在前面的示例中,我们定义了一个SaajSoapMessageFactory,它仅接受SOAP 1.2消息。spring-doc.cadn.net.cn

尽管两个版本的 SOAP 在格式上非常相似,但 1.2 版本与 1.1 版本并不向后兼容,因为它使用了不同的 XML 命名空间。 SOAP 1.1 和 1.2 之间的其他主要区别包括错误结构的不同以及 SOAPAction HTTP 头实际上已被弃用(尽管它们仍然可以工作)的事实。spring-doc.cadn.net.cn

需要注意的一件重要事情是,SOAP版本号(或WS-*规范版本号)的最新版本通常不是最流行的版本。 对于SOAP来说,这意味着(目前)最佳使用的版本是1.1。 虽然1.2版未来可能会变得更流行,但目前来看,使用1.1版是最稳妥的选择。spring-doc.cadn.net.cn

4.2.4. MessageContext

通常,消息是成对出现的:一个请求和一个响应。 请求是在客户端创建的,通过某种传输方式发送到服务器端,在服务器端生成响应。 该响应被发回客户端,并在客户端读取。spring-doc.cadn.net.cn

在 Spring-WS 中,这样的对话包含在一个 MessageContext 中,它具有获取请求和响应消息的属性。 在客户端,消息上下文由 WebServiceTemplate 创建。 在服务器端,消息上下文从特定传输的输入流中读取。 例如,在 HTTP 中,它从 HttpServletRequest 中读取,而响应则写回到 HttpServletResponsespring-doc.cadn.net.cn

4.3. TransportContext

SOAP 协议的关键特性之一是它试图做到传输无关性。 这就是为什么,例如,Spring-WS 不支持通过 HTTP 请求 URL 将消息映射到端点,而是通过消息内容进行映射。spring-doc.cadn.net.cn

然而,有时需要在客户端或服务器端访问底层传输。 为此,Spring-WS 提供了 TransportContext。 传输上下文允许访问底层的 WebServiceConnection,通常在服务器端是一个 HttpServletConnection,而在客户端则是一个 HttpUrlConnectionCommonsHttpConnection。 例如,您可以在服务器端的端点或拦截器中获取当前请求的 IP 地址:spring-doc.cadn.net.cn

TransportContext context = TransportContextHolder.getTransportContext();
HttpServletConnection connection = (HttpServletConnection )context.getConnection();
HttpServletRequest request = connection.getHttpServletRequest();
String ipAddress = request.getRemoteAddr();

4.4. 使用 XPath 处理 XML

处理 XML 的最佳方法之一是使用 XPath。 引用 [effective-xml],第 35 条:spring-doc.cadn.net.cn

XPath 是一种第四代声明性语言,它允许您指定要处理的节点,而无需明确说明处理器应该如何导航到这些节点。 XPath 的数据模型设计得非常出色,能够准确支持几乎所有开发者对 XML 的期望。 例如,它合并所有相邻的文本,包括 CDATA 部分中的文本,允许计算跳过注释和处理指令的值,并包含来自子元素和后代元素的文本,同时要求解析所有外部实体引用。 在实际应用中,XPath 表达式往往对输入文档中意外但可能无关紧要的变化具有更强的适应能力。
— Elliotte Rusty Harold

Spring-WS 在您的应用程序中有两种使用 XPath 的方式:更快的 XPathExpression 或更灵活的 XPathOperationsspring-doc.cadn.net.cn

4.4.1. XPathExpression

XPathExpression 是对编译后的 XPath 表达式的抽象,例如 Java 5 的 javax.xml.xpath.XPathExpression 接口或 Jaxen 的 XPath 类。 要在应用程序上下文中构造表达式,可以使用 XPathExpressionFactoryBean。 以下示例使用了这个工厂 bean:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
          http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">

    <bean id="nameExpression" class="org.springframework.xml.xpath.XPathExpressionFactoryBean">
        <property name="expression" value="/Contacts/Contact/Name"/>
    </bean>

    <bean id="myEndpoint" class="sample.MyXPathClass">
        <constructor-arg ref="nameExpression"/>
    </bean>

</beans>

前面的表达式没有使用命名空间,但我们可以使用工厂 bean 的 namespaces 属性来设置这些。 该表达式可以在代码中如下使用:spring-doc.cadn.net.cn

package sample;

public class MyXPathClass {

    private final XPathExpression nameExpression;

    public MyXPathClass(XPathExpression nameExpression) {
        this.nameExpression = nameExpression;
    }

    public void doXPath(Document document) {
        String name = nameExpression.evaluateAsString(document.getDocumentElement());
        System.out.println("Name: " + name);
    }

}

对于更灵活的方法,您可以使用NodeMapper,它类似于Spring的JDBC支持中的RowMapper。 以下示例展示了如何使用它:spring-doc.cadn.net.cn

package sample;

public class MyXPathClass  {

   private final XPathExpression contactExpression;

   public MyXPathClass(XPathExpression contactExpression) {
      this.contactExpression = contactExpression;
   }

   public void doXPath(Document document) {
      List contacts = contactExpression.evaluate(document,
        new NodeMapper() {
           public Object mapNode(Node node, int nodeNum) throws DOMException {
              Element contactElement = (Element) node;
              Element nameElement = (Element) contactElement.getElementsByTagName("Name").item(0);
              Element phoneElement = (Element) contactElement.getElementsByTagName("Phone").item(0);
              return new Contact(nameElement.getTextContent(), phoneElement.getTextContent());
           }
        });
      PlainText Section qName; // do something with the list of Contact objects
   }
}

类似于 Spring JDBC 的 RowMapper 中的映射行,每个结果节点通过使用匿名内部类进行映射。 在这种情况下,我们创建了一个 Contact 对象,稍后我们将使用它。spring-doc.cadn.net.cn

4.4.2. XPathOperations

XPathExpression 让您只能评估单个预先编译的表达式。 一个更灵活但较慢的替代方案是 XPathOperations。 该类遵循贯穿 Spring 的通用模板模式(JdbcTemplateJmsTemplate 等)。 以下清单展示了一个示例:spring-doc.cadn.net.cn

package sample;

public class MyXPathClass {

    private XPathOperations template = new Jaxp13XPathTemplate();

    public void doXPath(Source source) {
        String name = template.evaluateAsString("/Contacts/Contact/Name", request);
        // do something with name
    }

}

4.5. 消息日志记录与追踪

当开发或调试 Web 服务时,在消息(SOAP)到达时或发送之前查看其内容可能会非常有用。 Spring-WS 通过标准的 Commons Logging 接口提供了此功能。spring-doc.cadn.net.cn

要记录所有服务器端消息,请将org.springframework.ws.server.MessageTracing记录器级别设置为DEBUGTRACE。 在DEBUG级别,仅记录有效负载的根元素。 在TRACE级别,记录整个消息内容。 如果只想记录发送的消息,请使用org.springframework.ws.server.MessageTracing.sent记录器。 同样,可以使用org.springframework.ws.server.MessageTracing.received来仅记录接收到的消息。spring-doc.cadn.net.cn

在客户端,存在类似的记录器:org.springframework.ws.client.MessageTracing.sentorg.springframework.ws.client.MessageTracing.receivedspring-doc.cadn.net.cn

以下log4j2.properties配置文件的示例会在客户端记录发送消息的完整内容,而仅记录客户端接收到的消息的有效负载根元素。 在服务器端,发送和接收的消息都会记录有效负载根元素:spring-doc.cadn.net.cn

appender.console.name=STDOUT
appender.console.type=Console
appender.console.layout.type=PatternLayout
appender.console.layout.pattern=%-5p [%c{3}] %m%n

rootLogger=DEBUG,STDOUT
logger.org.springframework.ws.client.MessageTracing.sent=TRACE
logger.org.springframework.ws.client.MessageTracing.received=DEBUG
logger.org.springframework.ws.server.MessageTracing=DEBUG

通过此配置,典型的输出是:spring-doc.cadn.net.cn

TRACE [client.MessageTracing.sent] Sent request [<SOAP-ENV:Envelope xmlns:SOAP-ENV="...
DEBUG [server.MessageTracing.received] Received request [SaajSoapMessage {http://example.com}request] ...
DEBUG [server.MessageTracing.sent] Sent response [SaajSoapMessage {http://example.com}response] ...
DEBUG [client.MessageTracing.received] Received response [SaajSoapMessage {http://example.com}response] ...

5. 使用 Spring-WS 创建 Web 服务

Spring-WS 服务器端支持围绕一个 MessageDispatcher 设计,该组件将传入的消息分发到端点,并提供可配置的端点映射、响应生成和端点拦截功能。 端点通常使用 @Endpoint 注解进行标注,并具有一个或多个处理方法。 这些方法通过检查消息的某些部分(通常是有效负载)来处理传入的 XML 请求消息,并创建某种响应。 您可以使用另一个注解(通常是 @PayloadRoot)对方法进行注解,以指示它可以处理的消息类型。spring-doc.cadn.net.cn

Spring-WS XML 处理非常灵活。 端点可以从 Spring-WS 支持的大量 XML 处理库中进行选择,包括:spring-doc.cadn.net.cn

5.1. MessageDispatcher

Spring-WS 的服务器端围绕一个核心类设计,该类将传入的 XML 消息分派到端点。 Spring-WS MessageDispatcher 非常灵活,允许您使用任何类型的类作为端点,只要它可以在 Spring IoC 容器中配置即可。 在某种程度上,消息分发器类似于 Spring 的 DispatcherServlet,即 Spring Web MVC 中使用的“前端控制器”。spring-doc.cadn.net.cn

以下序列图展示了 MessageDispatcher 的处理和分发流程:spring-doc.cadn.net.cn

sequence

当一个MessageDispatcher被设置为可用,并且针对该特定调度器的请求到达时,MessageDispatcher开始处理请求。 以下过程描述了MessageDispatcher如何处理请求:spring-doc.cadn.net.cn

  1. 配置的 EndpointMapping(s) 将被搜索以找到合适的端点。 如果找到端点,则会调用与该端点关联的调用链(预处理器、后处理器和端点)以创建响应。spring-doc.cadn.net.cn

  2. 找到适合此端点的适配器。 MessageDispatcher 委托给该适配器以调用端点。spring-doc.cadn.net.cn

  3. 如果返回了响应,则会将其发送出去。 如果没有返回响应(例如,由于前置或后置处理器出于安全原因拦截了请求),则不会发送任何响应。spring-doc.cadn.net.cn

在处理请求期间抛出的异常会被应用程序上下文中声明的任何端点异常解析器捕获。 使用这些异常解析器可以让你定义自定义行为(例如返回一个 SOAP 错误),以防抛出此类异常。spring-doc.cadn.net.cn

MessageDispatcher 有多个用于设置端点适配器的属性,映射异常解析器。 然而,设置这些属性并不是必需的,因为调度器会自动检测应用程序上下文中注册的所有类型。 只有在需要覆盖检测时,才应设置这些属性。spring-doc.cadn.net.cn

消息分发器是基于消息上下文进行操作的,而不是基于特定传输的输入流和输出流。 因此,特定传输的请求需要读取到一个MessageContext中。 对于HTTP来说,这是通过一个WebServiceMessageReceiverHandlerAdapter(这是一个Spring Web的HandlerInterceptor)完成的,以便MessageDispatcher可以被装配到一个标准的DispatcherServlet中。 然而,有一种更便捷的方法可以实现这一点,如MessageDispatcherServlet所示。spring-doc.cadn.net.cn

5.2. 传输方式

Spring-WS 支持多种传输协议。 最常见的是 HTTP 传输,为此提供了自定义的 servlet,但您也可以通过 JMS 甚至电子邮件发送消息。spring-doc.cadn.net.cn

5.2.1. MessageDispatcherServlet

MessageDispatcherServlet 是一个标准的 Servlet,它方便地从标准的 Spring Web DispatcherServlet 扩展而来,并包装了一个 MessageDispatcher。 因此,它将这些属性组合在一起。 作为一个 MessageDispatcher,它遵循与上一节中描述的相同请求处理流程。 作为一个 servlet,MessageDispatcherServlet 在您的 Web 应用程序的 web.xml 中进行配置。 您希望 MessageDispatcherServlet 处理的请求必须通过同一个 web.xml 文件中的 URL 映射进行映射。 这是标准的 JavaEE servlet 配置。 以下示例展示了这样的 MessageDispatcherServlet 声明和映射:spring-doc.cadn.net.cn

<web-app>

    <servlet>
        <servlet-name>spring-ws</servlet-name>
        <servlet-class>org.springframework.ws.transport.http.MessageDispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>spring-ws</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

在前面的示例中,所有请求都由 spring-ws MessageDispatcherServlet 处理。 这只是设置 Spring-WS 的第一步,因为 Spring-WS 框架使用的各种组件 bean 也需要进行配置。 此配置包含标准的 Spring XML <bean/> 定义。 由于 MessageDispatcherServlet 是标准的 Spring DispatcherServlet,它会在您的 Web 应用程序的 WEB-INF 目录中查找名为 [servlet-name]-servlet.xml 的文件,并在 Spring 容器中创建那里定义的 bean。 在前面的示例中,它会查找 /WEB-INF/spring-ws-servlet.xml。 该文件包含所有的 Spring-WS bean,例如端点、编组器等。spring-doc.cadn.net.cn

作为 web.xml 的替代方法,您可以以编程方式配置 Spring-WS。 为此,Spring-WS 提供了一些抽象基类,这些基类扩展了 Spring 框架中的 WebApplicationInitializer 接口。 如果还使用 @Configuration 类来定义您的 bean,则应扩展 AbstractAnnotationConfigMessageDispatcherServletInitializerspring-doc.cadn.net.cn

public class MyServletInitializer
    extends AbstractAnnotationConfigMessageDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{MyRootConfig.class};
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{MyEndpointConfig.class};
    }

}

在前面的示例中,我们告诉 Spring 可以在 MyEndpointConfig 类(这是一个 @Configuration 类)中找到端点的 bean 定义。 其他 bean 定义(通常是服务、存储库等)可以在 MyRootConfig 类中找到。 默认情况下,AbstractAnnotationConfigMessageDispatcherServletInitializer 将 servlet 映射到两个模式:/services*.wsdl,不过您可以通过重写 getServletMappings() 方法来更改此行为。 有关 MessageDispatcherServlet 的编程配置的更多详细信息,请参阅 AbstractMessageDispatcherServletInitializerAbstractAnnotationConfigMessageDispatcherServletInitializer 的 Javadoc。spring-doc.cadn.net.cn

自动WSDL暴露

MessageDispatcherServlet 会自动检测其 Spring 容器中定义的任何 WsdlDefinition 类型的 bean。 所有检测到的 WsdlDefinition 类型的 bean 也会通过一个 WsdlDefinitionHandlerAdapter 暴露出来。 这是一种通过定义一些 bean 向客户端暴露 WSDL 的便捷方法。spring-doc.cadn.net.cn

通过一个示例来考虑以下 <static-wsdl> 定义,该定义在 Spring-WS 配置文件 (/WEB-INF/[servlet-name]-servlet.xml) 中。 请注意 id 属性的值,因为它在暴露 WSDL 时会被使用。spring-doc.cadn.net.cn

<sws:static-wsdl id="orders" location="orders.wsdl"/>

Alternatively, it can be a @Bean method in a @Configuration class:spring-doc.cadn.net.cn

@Bean
public SimpleWsdl11Definition orders() {
	return new SimpleWsdl11Definition(new ClassPathResource("orders.wsdl"));
}

您可以通过以下形式的URL(根据需要替换主机、端口和servlet上下文路径)以GET请求方式访问类路径中定义在orders.wsdl文件中的WSDL:spring-doc.cadn.net.cn

http://localhost:8080/spring-ws/orders.wsdl

所有 WsdlDefinition bean 定义都通过 MessageDispatcherServlet 以它们的 bean 名称加上后缀 `.wsdl` 的形式暴露出来。 因此,如果 bean 名称是 echo,主机名是 server,并且 Servlet 上下文(war 名称)是 spring-ws,那么 WSDL 可以在 http://server/spring-ws/echo.wsdl 找到。spring-doc.cadn.net.cn

MessageDispatcherServlet(或者更准确地说是WsdlDefinitionHandlerAdapter)的另一个不错的功能是,它可以转换所有公开的 WSDL 的location值,以反映传入请求的 URL。spring-doc.cadn.net.cn

请注意,默认情况下此 location 转换功能是关闭的。 要启用此功能,您需要为 MessageDispatcherServlet 指定一个初始化参数:spring-doc.cadn.net.cn

<web-app>

  <servlet>
    <servlet-name>spring-ws</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>

  <servlet-mapping>
    <servlet-name>spring-ws</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

</web-app>

如果使用 AbstractAnnotationConfigMessageDispatcherServletInitializer,启用转换就像重写 isTransformWsdlLocations() 方法以返回 true 一样简单。spring-doc.cadn.net.cn

Consult the class-level Javadoc on the WsdlDefinitionHandlerAdapter class to learn more about the whole transformation process.spring-doc.cadn.net.cn

作为手动编写 WSDL 并通过 <static-wsdl> 暴露它的替代方案,Spring-WS 还可以从 XSD 模式生成 WSDL。 这是在 发布 WSDL 中展示的方法。 下一个应用上下文片段展示了如何创建这样的动态 WSDL 文件:spring-doc.cadn.net.cn

<sws:dynamic-wsdl id="orders"
    portTypeName="Orders"
    locationUri="http://localhost:8080/ordersService/">
  <sws:xsd location="Orders.xsd"/>
</sws:dynamic-wsdl>

或者,您可以使用 Java @Bean 方法:spring-doc.cadn.net.cn

@Bean
public DefaultWsdl11Definition orders() {
    DefaultWsdl11Definition definition = new DefaultWsdl11Definition();
    definition.setPortTypeName("Orders");
    definition.setLocationUri("http://localhost:8080/ordersService/");
    definition.setSchema(new SimpleXsdSchema(new ClassPathResource("Orders.xsd")));
    return definition;
}

<dynamic-wsdl> 元素依赖于 DefaultWsdl11Definition 类。 这个定义类使用 org.springframework.ws.wsdl.wsdl11.provider 包中的 WSDL 提供者以及 ProviderBasedWsdl4jDefinition 类,在第一次请求时生成 WSDL。 如果需要,可以查看这些类的类级别 Javadoc,了解如何扩展此机制。spring-doc.cadn.net.cn

DefaultWsdl11Definition(因此,<dynamic-wsdl> 标签)通过使用约定从 XSD 模式构建 WSDL。 它会遍历模式中找到的所有 element 元素,并为所有元素创建一个 message。 接下来,它会为所有以定义的请求或响应后缀结尾的消息创建一个 WSDL operation。 默认的请求后缀是 Request。 默认的响应后缀是 Response,尽管可以通过分别在 <dynamic-wsdl /> 上设置 requestSuffixresponseSuffix 属性来更改这些值。 它还会根据操作构建 portTypebindingservicespring-doc.cadn.net.cn

例如,如果我们的 Orders.xsd 模式定义了 GetOrdersRequestGetOrdersResponse 元素,<dynamic-wsdl> 将创建一个 GetOrdersRequestGetOrdersResponse 消息以及一个 GetOrders 操作,这些会被放入 Orders 端口类型中。spring-doc.cadn.net.cn

要使用多个模式(schema),无论是通过 includes 还是 imports,都可以将 Commons XMLSchema 放在类路径中。 如果 Commons XMLSchema 在类路径中,<dynamic-wsdl> 元素会跟踪所有的 XSD 导入和包含,并将它们作为单个 XSD 内联到 WSDL 中。 使用 Java 配置时,可以像以下示例中那样使用 CommonsXsdSchemaCollectionspring-doc.cadn.net.cn

@Bean
public DefaultWsdl11Definition ordersWsdlDefinition() throws Exception {
    DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
    // ... configuration
    CommonsXsdSchemaCollection schemas = new CommonsXsdSchemaCollection(
			new ClassPathResource("xsd/order/main.xsd"));
    schemas.setInline(true);
    wsdl11Definition.setSchemaCollection(schemas);
    return wsdl11Definition;
}

这大大简化了模式的部署,同时仍然可以分别编辑它们。spring-doc.cadn.net.cn

尽管在运行时根据您的 XSD 生成 WSDL 很方便,但这种方法也有一些缺点。 首先,虽然我们努力在各个版本之间保持 WSDL 生成过程的一致性,但仍有可能发生(细微的)变化。 其次,生成过程有点慢,不过一旦生成,WSDL 将被缓存以供后续使用。spring-doc.cadn.net.cn

因此,您应该仅在项目的开发阶段使用 <dynamic-wsdl>。 我们建议使用浏览器下载生成的 WSDL,将其存储在项目中,并通过 <static-wsdl> 对其进行暴露。 这是确保 WSDL 不会随着时间变化的唯一方法。spring-doc.cadn.net.cn

5.2.2. 在 Spring-WS 中进行装配DispatcherServlet

作为 MessageDispatcherServlet 的替代方案,您可以在标准的 Spring-Web MVC DispatcherServlet 中连接一个 MessageDispatcher。 默认情况下,DispatcherServlet 只能委托给 Controllers,但我们可以指示它通过在 servlet 的 Web 应用程序上下文中添加一个 WebServiceMessageReceiverHandlerAdapter 来委托给 MessageDispatcherspring-doc.cadn.net.cn

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    ...

    <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

</beans>

请注意,通过显式添加WebServiceMessageReceiverHandlerAdapter,调度器 servlet 不会加载默认的适配器,因此无法处理标准的 Spring-MVC @Controllers。 因此,我们在最后添加了RequestMappingHandlerAdapterspring-doc.cadn.net.cn

同样地,您可以连接一个 WsdlDefinitionHandlerAdapter 以确保 DispatcherServlet 能够处理 WsdlDefinition 接口的实现:spring-doc.cadn.net.cn

<beans>

    <bean class="org.springframework.ws.transport.http.WebServiceMessageReceiverHandlerAdapter"/>

    <bean class="org.springframework.ws.transport.http.WsdlDefinitionHandlerAdapter"/>

    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
           <props>
             <prop key="*.wsdl">myServiceDefinition</prop>
           </props>
        </property>
        <property name="defaultHandler" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher"/>

    <bean id="myServiceDefinition" class="org.springframework.ws.wsdl.wsdl11.SimpleWsdl11Definition">
       <prop name="wsdl" value="/WEB-INF/myServiceDefinition.wsdl"/>
    </bean>

    ...

</beans>

5.2.3. JMS 传输

Spring-WS 通过 Spring 框架中提供的 JMS 功能支持服务端的 JMS 处理。 Spring-WS 提供了 WebServiceMessageListener 以连接到 MessageListenerContainer。 此消息监听器需要 WebServiceMessageFactoryMessageDispatcher 才能运行。 以下配置示例展示了这一点:spring-doc.cadn.net.cn

<beans>

    <bean id="connectionFactory" class="org.apache.activemq.ActiveMQConnectionFactory">
        <property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
    </bean>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean class="org.springframework.jms.listener.DefaultMessageListenerContainer">
        <property name="connectionFactory" ref="connectionFactory"/>
        <property name="destinationName" value="RequestQueue"/>
        <property name="messageListener">
            <bean class="org.springframework.ws.transport.jms.WebServiceMessageListener">
                <property name="messageFactory" ref="messageFactory"/>
                <property name="messageReceiver" ref="messageDispatcher"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.2.4. 邮件传输

除了 HTTP 和 JMS,Spring-WS 还提供了服务器端的邮件处理功能。 此功能通过 MailMessageReceiver 类提供。 该类监控一个 POP3 或 IMAP 文件夹,将电子邮件转换为 WebServiceMessage,并通过 SMTP 发送任何响应。 您可以通过 storeUri 配置主机名,该主机名指示用于监控请求的邮件文件夹(通常是 POP3 或 IMAP 文件夹),以及一个 transportUri,指示用于发送响应的服务器(通常是 SMTP 服务器)。spring-doc.cadn.net.cn

你可以通过一个可插拔的策略来配置MailMessageReceiver如何监控传入的消息:即MonitoringStrategy。 默认情况下,使用轮询策略,每五分钟轮询一次收件文件夹是否有新消息。 你可以通过在策略上设置pollingInterval属性来更改此间隔。 默认情况下,所有MonitoringStrategy实现都会删除已处理的消息。 你可以通过设置deleteMessages属性来更改此设置。spring-doc.cadn.net.cn

作为一种对效率较低的轮询方法的替代方案,有一种使用 IMAP IDLE 的监控策略。 IDLE 命令是 IMAP 电子邮件协议的可选扩展,它允许邮件服务器异步地向 MailMessageReceiver 发送新消息更新。 如果使用的 IMAP 服务器支持 IDLE 命令,则可以将 ImapIdleMonitoringStrategy 插入到 monitoringStrategy 属性中。spring-doc.cadn.net.cn

以下配置片段展示了如何使用服务器端的电子邮件支持,并将默认的轮询间隔覆盖为每30秒(30,000毫秒)检查一次:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.mail.MailMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="from" value="Spring-WS SOAP Server &lt;[email protected]&gt;"/>
        <property name="storeUri" value="imap://server:[email protected]/INBOX"/>
        <property name="transportUri" value="smtp://smtp.example.com"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
        <property name="monitoringStrategy">
            <bean class="org.springframework.ws.transport.mail.monitor.PollingMonitoringStrategy">
                <property name="pollingInterval" value="30000"/>
            </bean>
        </property>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>
</beans>

5.2.5. 嵌入式 HTTP 服务器传输

此内容仅应用于测试目的。

Spring-WS 提供了一个基于 Sun 的 JRE HTTP 服务器 的传输方式。 嵌入式 HTTP 服务器是一个独立的服务器,配置简单。 它提供了一种比传统 servlet 容器更轻量的替代方案。spring-doc.cadn.net.cn

当使用嵌入式HTTP服务器时,不需要外部部署描述符(web.xml)。 你只需要定义一个服务器实例,并配置它来处理传入的请求。 SimpleHttpServerFactoryBean将各部分连接起来,其中最重要的属性是contexts,它将上下文路径映射到相应的HttpHandler实例。spring-doc.cadn.net.cn

Spring-WS 提供了两种 HttpHandler 接口的实现:WsdlDefinitionHttpHandlerWebServiceMessageReceiverHttpHandler。 前者将传入的 GET 请求映射到 WsdlDefinition。 后者负责处理 Web 服务消息的 POST 请求,因此需要一个 WebServiceMessageFactory(通常是一个 SaajSoapMessageFactory)和一个 WebServiceMessageReceiver(通常是 SoapMessageDispatcher)来完成其任务。spring-doc.cadn.net.cn

为了与servlet世界进行类比,contexts属性在web.xml中扮演了servlet映射的角色,而WebServiceMessageReceiverHttpHandler等同于一个MessageDispatcherServletspring-doc.cadn.net.cn

以下代码片段展示了HTTP服务器传输的配置示例:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="messageReceiver" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings" ref="endpointMapping"/>
    </bean>

    <bean id="endpointMapping" class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
        <property name="defaultEndpoint" ref="stockEndpoint"/>
    </bean>

    <bean id="httpServer" class="org.springframework.ws.transport.http.SimpleHttpServerFactoryBean">
        <property name="contexts">
            <map>
                <entry key="/StockService.wsdl" value-ref="wsdlHandler"/>
                <entry key="/StockService" value-ref="soapHandler"/>
            </map>
        </property>
    </bean>

    <bean id="soapHandler" class="org.springframework.ws.transport.http.WebServiceMessageReceiverHttpHandler">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="messageReceiver" ref="messageReceiver"/>
    </bean>

    <bean id="wsdlHandler" class="org.springframework.ws.transport.http.WsdlDefinitionHttpHandler">
        <property name="definition" ref="wsdlDefinition"/>
    </bean>
</beans>

5.2.6. XMPP 传输

Spring-WS 支持 XMPP,也称为 Jabber。 该支持基于 Smack 库。spring-doc.cadn.net.cn

Spring-WS 对 XMPP 的支持与其他传输方式非常相似:有一个 XmppMessageSender 用于 WebServiceTemplate,还有一个 XmppMessageReceiver 用于与 MessageDispatcher 配合使用。spring-doc.cadn.net.cn

以下示例展示了如何设置服务器端的 XMPP 组件:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="messagingReceiver" class="org.springframework.ws.transport.xmpp.XmppMessageReceiver">
        <property name="messageFactory" ref="messageFactory"/>
        <property name="connection" ref="connection"/>
        <property name="messageReceiver" ref="messageDispatcher"/>
    </bean>

    <bean id="messageDispatcher" class="org.springframework.ws.soap.server.SoapMessageDispatcher">
        <property name="endpointMappings">
            <bean
              class="org.springframework.ws.server.endpoint.mapping.PayloadRootAnnotationMethodEndpointMapping">
                <property name="defaultEndpoint">
                    <bean class="com.example.MyEndpoint"/>
                </property>
            </bean>
        </property>
    </bean>

</beans>

5.2.7. MTOM

MTOM 是用于向 Web 服务发送和接收二进制数据的机制。 您可以通过 MTOM 示例 了解如何使用 Spring WS 实现这一功能。spring-doc.cadn.net.cn

5.3. 端点

Endpoints 是 Spring-WS 服务器端支持的核心概念。 Endpoints 提供对应用程序行为的访问,这些行为通常由业务服务接口定义。 Endpoint 解析 XML 请求消息,并使用该输入(通常)调用业务服务上的方法。 该服务调用的结果表示为响应消息。 Spring-WS 拥有各种各样的 Endpoints,并采用多种方式处理 XML 消息以及创建响应。spring-doc.cadn.net.cn

你可以通过使用@Endpoint注解标注一个类来创建端点。 在类中,你可以定义一个或多个方法来处理传入的XML请求,方法可以使用多种参数类型(例如DOM元素、JAXB2对象等)。 你可以通过使用另一个注解(通常是@PayloadRoot)来指明某个方法可以处理的消息类型。spring-doc.cadn.net.cn

考虑以下示例端点:spring-doc.cadn.net.cn

package samples;

import org.w3c.dom.Element;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.soap.SoapHeader;

@Endpoint (1)
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  public AnnotationOrderEndpoint(OrderService orderService) {
      this.orderService = orderService;
  }

  @PayloadRoot(localPart = "order", namespace = "http://samples") (4)
  public void order(@RequestPayload Element orderElement) { (2)
    Order order = createOrder(orderElement);
    orderService.createOrder(order);
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples") (4)
  @ResponsePayload
  public Order getOrder(@RequestPayload OrderRequest orderRequest, SoapHeader header) { (3)
    checkSoapHeaderForSomething(header);
    return orderService.getOrder(orderRequest.getId());
  }

}
1 类使用 @Endpoint 注解标记,将其标识为 Spring-WS 端点。
2 The order 方法接收一个 Element (使用 @RequestPayload 注解)作为参数。 这意味着消息的有效载荷会作为 DOM 元素传递给该方法。 该方法的返回类型为 void,表示不会发送响应消息。 有关端点方法的更多信息,请参阅 @Endpoint 处理方法
3 getOrder 方法接收一个 OrderRequest(同样用 @RequestPayload 注解)作为参数。 该参数是一个受 JAXB2 支持的对象(它用 @XmlRootElement 注解)。 这意味着消息的负载会以未编组对象的形式传递给此方法。 SoapHeader 类型也作为参数给出。 在调用时,该参数包含请求消息的 SOAP 头。 该方法还用 @ResponsePayload 注解,表明返回值(即 Order)被用作响应消息的负载。 有关端点方法的更多信息,请参阅 @Endpoint 处理方法
4 此端点的两种处理方法被标记为 @PayloadRoot,表示该方法可以处理何种请求消息:对于具有 orderRequest 本地名称和 http://samples 命名空间 URI 的请求,将调用 getOrder 方法。 对于具有 order 本地名称的请求,将调用 order 方法。 有关 @PayloadRoot 的更多信息,请参见 端点映射

要启用对 @Endpoint 和相关的 Spring-WS 注解的支持,您需要将以下内容添加到您的 Spring 应用程序上下文中:spring-doc.cadn.net.cn

<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:sws="http://www.springframework.org/schema/web-services"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans.xsd
    *http://www.springframework.org/schema/web-services
      http://www.springframework.org/schema/web-services/web-services.xsd">

  <sws:annotation-driven />

</beans>

或者,如果你使用 @Configuration 类而不是 Spring XML,则可以使用 @EnableWs 注解你的配置类:spring-doc.cadn.net.cn

@EnableWs
@Configuration
public class EchoConfig {

    // @Bean definitions go here

}

要自定义 @EnableWs 配置,您可以实现 WsConfigurer 并重写个别方法:spring-doc.cadn.net.cn

@Configuration
@EnableWs
public class EchoConfig implements WsConfigurer {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyInterceptor());
  }

  @Override
  public void addArgumentResolvers(List<MethodArgumentResolver> argumentResolvers) {
    argumentResolvers.add(new MyArgumentResolver());
  }

}

如果WsConfigurer没有暴露一些需要配置的更高级的设置,考虑移除@EnableWs并直接从WsConfigurationSupportDelegatingWsConfiguration扩展。spring-doc.cadn.net.cn

@Configuration
public class EchoConfig extends WsConfigurationSupport {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyInterceptor());
  }

  @Bean
  @Override
  public PayloadRootAnnotationMethodEndpointMapping payloadRootAnnotationMethodEndpointMapping() {
      // Create or delegate to "super" to create and
      // customize properties of PayloadRootAnnotationMethodEndpointMapping
  }

}

在接下来的几个部分中,将对@Endpoint编程模型进行更详细的描述。spring-doc.cadn.net.cn

Endpoints, 像其他任何 Spring Bean 一样,默认情况下是单例作用域的。 也就是说,每个容器只创建一次该 bean 定义的实例。 作为单例意味着可能有多个线程同时使用它,因此该端点必须是线程安全的。 如果你想使用不同的作用域,例如 prototype,请参阅Spring Framework 参考文档spring-doc.cadn.net.cn

请注意,Spring-WS 中提供的所有抽象基类都是线程安全的,除非在类级别的 Javadoc 中另有说明。spring-doc.cadn.net.cn

5.4. @Endpoint 处理方法

为了使某个端点能够实际处理传入的 XML 消息,它需要具有一个或多个处理方法。 处理方法可以接受多种参数和返回类型。 然而,它们通常会有一个包含消息负载的参数,并返回响应消息的负载(如果有)。 本节将介绍支持哪些参数和返回类型。spring-doc.cadn.net.cn

为了指示方法可以处理何种类型的消息,通常使用@PayloadRoot@SoapAction注解对方法进行标注。 您可以从端点映射中了解更多关于这些注解的信息。spring-doc.cadn.net.cn

以下示例展示了一个处理方法:spring-doc.cadn.net.cn

@PayloadRoot(localPart = "order", namespace = "http://samples")
public void order(@RequestPayload Element orderElement) {
  Order order = createOrder(orderElement);
  orderService.createOrder(order);
}

The order 方法接受一个 Element (使用 @RequestPayload 注解)作为参数。 这意味着消息的有效载荷将作为 DOM 元素传递给该方法。 该方法的返回类型为 void,表示不会发送响应消息。spring-doc.cadn.net.cn

5.4.1. 处理方法参数

处理方法通常具有一个或多个参数,这些参数引用了传入XML消息的各个部分。 最常见的情况是,处理方法具有单一参数,该参数映射到消息的有效负载,但它也可以映射到请求消息的其他部分,例如SOAP头。 本节描述了您可以在处理方法签名中使用的参数。spring-doc.cadn.net.cn

要将参数映射到请求消息的有效负载,您需要使用 @RequestPayload 注解对该参数进行注释。 此注解告诉 Spring-WS 该参数需要绑定到请求的有效负载。spring-doc.cadn.net.cn

下表描述了支持的参数类型。 它显示了支持的类型,参数是否应使用 @RequestPayload 进行注解,以及任何其他注意事项。spring-doc.cadn.net.cn

姓名 支持的参数类型 @RequestPayload 是必需的吗? 附加说明

TrAXspring-doc.cadn.net.cn

javax.xml.transform.Source 和子接口 (DOMSource, SAXSource, StreamSource, 和 StAXSource)spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

W3C DOMspring-doc.cadn.net.cn

org.w3c.dom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用spring-doc.cadn.net.cn

dom4jspring-doc.cadn.net.cn

org.dom4j.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 dom4j 在类路径中时启用。spring-doc.cadn.net.cn

JDOMspring-doc.cadn.net.cn

org.jdom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JDOM 在类路径中时启用。spring-doc.cadn.net.cn

XOMspring-doc.cadn.net.cn

nu.xom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 XOM 在类路径中时启用。spring-doc.cadn.net.cn

StAXspring-doc.cadn.net.cn

javax.xml.stream.XMLStreamReaderjavax.xml.stream.XMLEventReaderspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 StAX 在类路径中时启用。spring-doc.cadn.net.cn

XPathspring-doc.cadn.net.cn

任何布尔值、双精度浮点数、Stringorg.w3c.Nodeorg.w3c.dom.NodeList,或者可以通过 Spring 的 转换服务String 转换而来的类型,并且使用 @XPathParam 注解。spring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认情况下已启用,参见名为XPathParam的部分spring-doc.cadn.net.cn

消息上下文spring-doc.cadn.net.cn

org.springframework.ws.context.MessageContextspring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

SOAPspring-doc.cadn.net.cn

org.springframework.ws.soap.SoapMessage, org.springframework.ws.soap.SoapBody, org.springframework.ws.soap.SoapEnvelope, org.springframework.ws.soap.SoapHeader, 和 org.springframework.ws.soap.SoapHeaderElement`s when used in combination with the `@SoapHeader 注解。spring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

JAXB2spring-doc.cadn.net.cn

任何使用 javax.xml.bind.annotation.XmlRootElementjavax.xml.bind.JAXBElement 注解的类型。spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JAXB2 在类路径中时启用。spring-doc.cadn.net.cn

OXMspring-doc.cadn.net.cn

任何受 Spring OXM 支持的类型 Unmarshallerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当指定了 <sws:annotation-driven/>unmarshaller 属性时启用。spring-doc.cadn.net.cn

接下来的几个示例展示了可能的方法签名。 以下方法在请求消息的有效负载作为 DOM org.w3c.dom.Element 调用时触发:spring-doc.cadn.net.cn

public void handle(@RequestPayload Element element)

以下方法使用请求消息的有效负载作为 javax.xml.transform.dom.DOMSource 调用。 header 参数绑定到请求消息的 SOAP 头部。spring-doc.cadn.net.cn

public void handle(@RequestPayload DOMSource domSource, SoapHeader header)

以下方法被调用时,请求消息的有效负载会被解组为一个MyJaxb2Object(其使用@XmlRootElement进行注解)。 消息的有效负载还会作为 DOM Element 提供。 整个消息上下文作为第三个参数传递。spring-doc.cadn.net.cn

public void handle(@RequestPayload MyJaxb2Object requestObject, @RequestPayload Element element, Message messageContext)

正如您所见,在定义如何处理方法签名时,有很多可能性。 您甚至可以扩展此机制以支持您自己的参数类型。 请参阅DefaultMethodEndpointAdapterMethodArgumentResolver 的 Javadoc,了解具体方法。spring-doc.cadn.net.cn

@XPathParam

需要特别说明的一个参数类型是:@XPathParam。 这里的想法是,你可以使用XPath表达式注解一个或多个方法参数,并且每个这样的注解参数都会绑定到该表达式的求值结果。 下面的例子展示了如何实现这一点:spring-doc.cadn.net.cn

package samples;

import javax.xml.transform.Source;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.Namespace;
import org.springframework.ws.server.endpoint.annotation.PayloadRoot;
import org.springframework.ws.server.endpoint.annotation.XPathParam;

@Endpoint
public class AnnotationOrderEndpoint {

  private final OrderService orderService;

  public AnnotationOrderEndpoint(OrderService orderService) {
    this.orderService = orderService;
  }

  @PayloadRoot(localPart = "orderRequest", namespace = "http://samples")
  @Namespace(prefix = "s", uri="http://samples")
  public Order getOrder(@XPathParam("/s:orderRequest/@id") int orderId) {
    Order order = orderService.getOrder(orderId);
    // create Source from order and return it
  }

}

由于我们在XPath表达式中使用了s前缀,因此必须将其绑定到http://samples命名空间。 这是通过@Namespace注解实现的。 或者,我们可以将此注解放置在类型级别,以便对所有处理器方法使用相同的命名空间映射,甚至可以放在包级别(在package-info.java中),以用于多个端点。spring-doc.cadn.net.cn

通过使用 @XPathParam,您可以绑定 XPath 支持的所有数据类型:spring-doc.cadn.net.cn

除了此列表之外,您还可以使用任何可以通过 Spring 转换服务String 转换的类型。spring-doc.cadn.net.cn

5.4.2. 处理方法返回类型

为了发送响应消息,处理方法需要指定一个返回类型。 如果不需要响应消息,该方法可以声明为 void 返回类型。 最常见的情况是,返回类型用于创建响应消息的有效载荷。 然而,您还可以映射到响应消息的其他部分。 本节描述了您可以在处理方法签名中使用的返回类型。spring-doc.cadn.net.cn

要将返回值映射到响应消息的有效载荷,您需要使用@ResponsePayload注解对方法进行注释。 此注解告诉Spring-WS返回值需要绑定到响应有效载荷。spring-doc.cadn.net.cn

下表描述了支持的返回类型。 它显示了支持的类型,参数是否应使用 @ResponsePayload 进行注解,以及任何其他注意事项。spring-doc.cadn.net.cn

姓名 支持的返回类型 @ResponsePayload 是必需的吗? 附加说明

无响应spring-doc.cadn.net.cn

voidspring-doc.cadn.net.cn

Nospring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

TrAXspring-doc.cadn.net.cn

javax.xml.transform.Source 和子接口 (DOMSource, SAXSource, StreamSource, 和 StAXSource)spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用。spring-doc.cadn.net.cn

W3C DOMspring-doc.cadn.net.cn

org.w3c.dom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

默认启用spring-doc.cadn.net.cn

dom4jspring-doc.cadn.net.cn

org.dom4j.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 dom4j 在类路径中时启用。spring-doc.cadn.net.cn

JDOMspring-doc.cadn.net.cn

org.jdom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JDOM 在类路径中时启用。spring-doc.cadn.net.cn

XOMspring-doc.cadn.net.cn

nu.xom.Elementspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 XOM 在类路径中时启用。spring-doc.cadn.net.cn

JAXB2spring-doc.cadn.net.cn

任何使用 javax.xml.bind.annotation.XmlRootElementjavax.xml.bind.JAXBElement 注解的类型。spring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当 JAXB2 在类路径中时启用。spring-doc.cadn.net.cn

OXMspring-doc.cadn.net.cn

任何受 Spring OXM 支持的类型 Marshallerspring-doc.cadn.net.cn

是的spring-doc.cadn.net.cn

当指定了 <sws:annotation-driven/>marshaller 属性时启用。spring-doc.cadn.net.cn

定义处理方法签名时有很多可能性。 甚至可以扩展此机制以支持您自己的参数类型。 请参阅DefaultMethodEndpointAdapterMethodReturnValueHandler 的类级 Javadoc 以了解如何操作。spring-doc.cadn.net.cn

5.5. 端点映射

端点映射负责将传入的消息映射到适当的端点。 某些端点映射默认是启用的——例如,PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping。 但是,我们首先需要检查 EndpointMapping 的一般概念。spring-doc.cadn.net.cn

一个 EndpointMapping 提供了一个 EndpointInvocationChain,其中包含了与传入请求匹配的端点,也可能包含应用于请求和响应的端点拦截器列表。 当请求到达时,MessageDispatcher 将其交给端点映射(endpoint mapping)以检查请求并找到合适的 EndpointInvocationChain。 然后 MessageDispatcher 调用该端点以及链中的任何拦截器。spring-doc.cadn.net.cn

可配置的端点映射概念可以选择性地包含拦截器(这些拦截器可以进一步操作请求、响应或同时操作两者),这一概念非常强大。 许多辅助功能可以构建到自定义 EndpointMapping 实现中。 例如,一个自定义端点映射不仅可以根据消息的内容选择端点,还可以基于特定的 SOAP 头(或者,实际上是多个 SOAP 头)来选择。spring-doc.cadn.net.cn

大多数端点映射继承自AbstractEndpointMapping,它提供了一个“interceptors”属性,这是要使用的拦截器列表。 EndpointInterceptors拦截请求——EndpointInterceptor接口中进行了讨论。spring-doc.cadn.net.cn

正如在端点中所解释的,@Endpoint风格允许您在一个端点类中处理多个请求。 这是MethodEndpointMapping的责任。 此映射确定了针对传入请求消息要调用哪个方法。spring-doc.cadn.net.cn

有两种端点映射可以将请求定向到方法:即 PayloadRootAnnotationMethodEndpointMappingSoapActionAnnotationMethodEndpointMapping。您可以通过在应用上下文中使用 <sws:annotation-driven/> 来启用这两种方法。spring-doc.cadn.net.cn

PayloadRootAnnotationMethodEndpointMapping 使用 @PayloadRoot 注解,并结合 localPartnamespace 元素,来标记具有特定限定名称的方法。 每当具有此限定名称的消息作为有效负载根元素传入时,该方法将被调用。spring-doc.cadn.net.cn

或者,SoapActionAnnotationMethodEndpointMapping 使用 @SoapAction 注解来标记具有特定 SOAP Action 的方法。 每当带有此 SOAPAction 标头的消息传入时,该方法就会被调用。spring-doc.cadn.net.cn

AbstractEndpointMapping 实现提供了一个 defaultEndpoint 属性,该属性用于配置在配置的映射未导致匹配的端点时使用的端点。spring-doc.cadn.net.cn

5.5.1. WS-Addressing

WS-Addressing 指定了一个与传输无关的路由机制。它基于 ToAction SOAP 头,分别指示 SOAP 消息的目标和意图。此外,WS-Addressing 允许您定义一个返回地址(用于普通消息和错误消息)以及一个唯一的消息标识符,该标识符可用于关联。有关 WS-Addressing 的更多信息,请参见 https://en.wikipedia.org/wiki/WS-Addressing。以下示例展示了一个 WS-Addressing 消息:spring-doc.cadn.net.cn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://www.w3.org/2003/05/soap-envelope"
    xmlns:wsa="http://www.w3.org/2005/08/addressing">
  <SOAP-ENV:Header>
    <wsa:MessageID>urn:uuid:21363e0d-2645-4eb7-8afd-2f5ee1bb25cf</wsa:MessageID>
    <wsa:ReplyTo>
      <wsa:Address>http://example.com/business/client1</wsa:Address>
    </wsa:ReplyTo>
    <wsa:To S:mustUnderstand="true">http://example/com/fabrikam</wsa:To>
    <wsa:Action>http://example.com/fabrikam/mail/Delete</wsa:Action>
  </SOAP-ENV:Header>
  <SOAP-ENV:Body>
    <f:Delete xmlns:f="http://example.com/fabrikam">
      <f:maxCount>42</f:maxCount>
    </f:Delete>
  </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

在前面的示例中,目标被设置为 http://example/com/fabrikam,而动作被设置为 http://example.com/fabrikam/mail/Delete。 此外,还有一个消息标识符和一个回复地址。 默认情况下,该地址是“匿名”地址,表示响应应该使用与请求相同的通道发送(即 HTTP 响应),但也可以是另一个地址,如本例所示。spring-doc.cadn.net.cn

在 Spring-WS 中,WS-Addressing 被实现为一个端点映射。通过使用此映射,您可以将 WS-Addressing 操作与端点关联起来,类似于之前描述的 SoapActionAnnotationMethodEndpointMappingspring-doc.cadn.net.cn

使用AnnotationActionEndpointMapping

AnnotationActionEndpointMappingSoapActionAnnotationMethodEndpointMapping 类似,但使用 WS-Addressing 标头而不是 SOAP Action 传输标头。spring-doc.cadn.net.cn

要使用AnnotationActionEndpointMapping,请使用@Action注解对处理方法进行注解,类似于@Endpoint处理方法端点映射中描述的@PayloadRoot@SoapAction注解。 以下示例展示了如何实现:spring-doc.cadn.net.cn

package samples;

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.soap.addressing.server.annotation.Action

@Endpoint
public class AnnotationOrderEndpoint {

    private final OrderService orderService;

    public AnnotationOrderEndpoint(OrderService orderService) {
        this.orderService = orderService;
    }

    @Action("http://samples/RequestOrder")
    public Order getOrder(OrderRequest orderRequest) {
        return orderService.getOrder(orderRequest.getId());
    }

    @Action("http://samples/CreateOrder")
    public void order(Order order) {
        orderService.createOrder(order);
    }

}

前面的映射将具有 WS-Addressing Actionhttp://samples/RequestOrder 的请求路由到 getOrder 方法。 http://samples/CreateOrder 的请求被路由到 order 方法。spring-doc.cadn.net.cn

默认情况下,AnnotationActionEndpointMapping 同时支持 WS-Addressing 的 1.0(2006 年 5 月)和 2004 年 8 月版。 这两个版本最为流行,并且与 Axis 1 和 2、JAX-WS、XFire、Windows Communication Foundation (WCF) 以及 Windows Services Enhancements (WSE) 3.0 兼容。 如有必要,可以将特定版本的规范注入到 versions 属性中。spring-doc.cadn.net.cn

除了使用 @Action 注解之外,您还可以使用 @Address 注解对类进行注解。 如果设置了该值,则会将其与传入消息的 To 头部属性进行比较。spring-doc.cadn.net.cn

最后,有一个必需的 messageSenders 属性,用于向非匿名的、带外地址发送响应消息。 您可以在此属性中设置 MessageSender 实现,与在 WebServiceTemplate 上的操作相同。 参见 URI 和传输spring-doc.cadn.net.cn

5.5.2. 拦截请求 —— EndpointInterceptor接口

端点映射机制具有端点拦截器的概念。这些拦截器在您希望对某些请求应用特定功能时非常有用——例如,处理与安全相关的SOAP头或记录请求和响应消息。spring-doc.cadn.net.cn

Endpoint interceptors are typically defined by using a <sws:interceptors> element in your application context. In this element, you can define endpoint interceptor beans that apply to all endpoints defined in that application context. Alternatively, you can use <sws:payloadRoot> or <sws:soapAction> elements to specify for which payload root name or SOAP action the interceptor should apply. The following example shows how to do so:spring-doc.cadn.net.cn

<sws:interceptors>
  <bean class="samples.MyGlobalInterceptor"/>
  <sws:payloadRoot namespaceUri="http://www.example.com">
    <bean class="samples.MyPayloadRootInterceptor"/>
  </sws:payloadRoot>
  <sws:soapAction value="http://www.example.com/SoapAction">
    <bean class="samples.MySoapActionInterceptor1"/>
    <ref bean="mySoapActionInterceptor2"/>
  </sws:soapAction>
</sws:interceptors>

<bean id="mySoapActionInterceptor2" class="samples.MySoapActionInterceptor2"/>

在前面的示例中,我们定义了一个“全局”拦截器(MyGlobalInterceptor),它拦截所有的请求和响应。 我们还定义了一个仅适用于以 http://www.example.com 作为有效负载根命名空间的 XML 消息的拦截器。 除了 namespaceUri 属性之外,我们还可以定义一个 localPart 属性,以进一步限制该拦截器适用的消息范围。 最后,我们定义了两个在消息具有 http://www.example.com/SoapAction SOAP 动作时应用的拦截器。 请注意,第二个拦截器实际上是对 <interceptors> 元素外部的 bean 定义的引用。 您可以在 <interceptors> 元素内的任何位置使用 bean 引用。spring-doc.cadn.net.cn

当您使用 @Configuration 类时,可以实现 WsConfigurer 来添加拦截器:spring-doc.cadn.net.cn

@Configuration
@EnableWs
public class MyWsConfiguration implements WsConfigurer {

  @Override
  public void addInterceptors(List<EndpointInterceptor> interceptors) {
    interceptors.add(new MyPayloadRootInterceptor());
  }

}

拦截器必须实现 EndpointInterceptor。 此接口定义了三个方法,一个可用于在实际端点处理之前处理请求消息,一个可用于处理正常的响应消息,另一个可用于处理错误消息。 后两个方法在端点处理之后调用。 这三种方法应提供足够的灵活性以进行各种预处理和后处理。spring-doc.cadn.net.cn

拦截器中的 handleRequest(..) 方法返回一个布尔值。 你可以使用此方法中断或继续调用链的处理。 当此方法返回 true 时,端点处理链将继续。 当它返回 false 时,MessageDispatcher 会解释为该拦截器自身已处理好相关逻辑,并且不再继续处理调用链中的其他拦截器和实际端点。 handleResponse(..)handleFault(..) 方法也具有布尔返回值。 当这些方法返回 false 时,响应将不会发送回客户端。spring-doc.cadn.net.cn

有许多标准的 EndpointInterceptor 实现可以在您的 Web 服务中使用。 此外,还有 Wss4jSecurityInterceptor,它在 使用 Spring-WS 保护您的 Web 服务 中有所描述。spring-doc.cadn.net.cn

PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor

当开发一个Web服务时,记录传入和传出的XML消息可能会很有用。 Spring WS通过PayloadLoggingInterceptorSoapEnvelopeLoggingInterceptor类来简化这一操作。 前者仅记录消息的有效负载。 后者记录整个SOAP信封,包括SOAP头。 以下示例展示了如何在端点映射中定义PayloadLoggingInterceptorspring-doc.cadn.net.cn

  <sws:interceptors>
    <bean class="org.springframework.ws.server.endpoint.interceptor.PayloadLoggingInterceptor"/>
  </sws:interceptors>

这两个拦截器都有两个属性,logRequestlogResponse,可以将它们设置为 false 以禁用对请求或响应消息的日志记录。spring-doc.cadn.net.cn

你可以使用之前描述的 WsConfigurer 方法,同样适用于 PayloadLoggingInterceptorspring-doc.cadn.net.cn

PayloadValidatingInterceptor

使用契约优先开发风格的好处之一是,我们可以使用模式来验证传入和传出的 XML 消息。 Spring-WS 通过 PayloadValidatingInterceptor 提供了这一功能。 此拦截器需要引用一个或多个 W3C XML 或 RELAX NG 模式,并且可以设置为验证请求、响应或两者。spring-doc.cadn.net.cn

虽然请求验证听起来像是个好主意,但它会使生成的Web服务非常严格。 通常,请求是否通过验证并不重要,重要的是端点能否获取足够的信息来满足请求。 验证响应是一个好主意,因为端点应遵循其模式。 记住波斯特法则:“对自己所做的要保守,对他人接受的要宽松。”spring-doc.cadn.net.cn

以下示例使用了 PayloadValidatingInterceptor。 在此示例中,我们使用 /WEB-INF/orders.xsd 中的模式来验证响应而不是请求。 请注意,PayloadValidatingInterceptor 也可以通过设置 schemas 属性来接受多个模式。spring-doc.cadn.net.cn

<bean id="validatingInterceptor"
        class="org.springframework.ws.soap.server.endpoint.interceptor.PayloadValidatingInterceptor">
    <property name="schema" value="/WEB-INF/orders.xsd"/>
    <property name="validateRequest" value="false"/>
    <property name="validateResponse" value="true"/>
</bean>

当然,您也可以像前面所描述的那样,对PayloadValidatingInterceptor采用WsConfigurer方法。spring-doc.cadn.net.cn

使用PayloadTransformingInterceptor

为了将有效负载转换为另一种XML格式,Spring-WS提供了PayloadTransformingInterceptor。 这个端点拦截器基于XSLT样式表,特别适用于支持多个版本的Web服务,因为您可以将旧的消息格式转换为新的格式。 以下示例使用了PayloadTransformingInterceptorspring-doc.cadn.net.cn

<bean id="transformingInterceptor"
        class="org.springframework.ws.server.endpoint.interceptor.PayloadTransformingInterceptor">
    <property name="requestXslt" value="/WEB-INF/oldRequests.xslt"/>
    <property name="responseXslt" value="/WEB-INF/oldResponses.xslt"/>
</bean>

在前面的示例中,我们通过使用 /WEB-INF/oldRequests.xslt 转换请求,并通过使用 /WEB-INF/oldResponses.xslt 转换响应消息。 请注意,由于端点拦截器是在端点映射级别注册的,因此您可以创建一个适用于“旧样式”消息的端点映射,并将拦截器添加到该映射中。 因此,该转换仅适用于这些“旧样式”消息。spring-doc.cadn.net.cn

你可以使用之前描述的 WsConfigurer 方法,同样适用于 PayloadTransformingInterceptorspring-doc.cadn.net.cn

5.6. 异常处理

Spring-WS 提供了 EndpointExceptionResolver 实现,以减轻在消息被与请求匹配的端点处理时发生意外异常的痛苦。 端点异常解析器在某种程度上类似于可以在 Web 应用程序描述符 web.xml 中定义的异常映射。 然而,它们提供了更灵活的方式来处理异常。 它们提供了关于抛出异常时调用了哪个端点的信息。 此外,通过编程方式处理异常为您提供了更多关于如何适当响应的选择。 与其通过暴露异常和堆栈跟踪来揭示应用程序的内部结构,您可以按照自己的方式处理异常 —— 例如,返回具有特定故障代码和字符串的 SOAP 错误。spring-doc.cadn.net.cn

Endpoint exception resolvers are automatically picked up by the MessageDispatcher, so no explicit configuration is necessary.spring-doc.cadn.net.cn

除了实现EndpointExceptionResolver接口(这仅仅需要实现resolveException(MessageContext, endpoint, Exception)方法)之外,您还可以使用提供的实现之一。spring-doc.cadn.net.cn

最简单的实现是 SimpleSoapExceptionResolver,它创建一个 SOAP 1.1 服务器或 SOAP 1.2 接收方错误,并使用异常消息作为错误字符串。您可以对其进行子类化以自定义错误,如以下示例所示:spring-doc.cadn.net.cn

public class CustomSoapExceptionResolver extends SimpleSoapExceptionResolver {

	private final Transformer transformer;

	public CustomSoapExceptionResolver(Transformer transformer) {
		this.transformer = transformer;
	}

	@Override
	protected void customizeFault(MessageContext messageContext, Object endpoint, Exception exception,
			SoapFault fault) {
		SoapFaultDetail faultDetail = fault.addFaultDetail();
		try {
			this.transformer.transform(new StringSource("""
					<ns2:YourCustomException xmlns:ns2="http://serviceendpoint/">
						<errorCode>Your custom error code</errorCode>
						<systemMessage>A system message</systemMessage>
					 </ns2:YourCustomException >
					"""), faultDetail.getResult());
		}
		catch (TransformerException ex) {
			throw new IllegalArgumentException("Failed to write detail", ex);
		}

	}
}

默认值为 SimpleSoapExceptionResolver,但可以通过显式添加其他解析器来覆盖它。spring-doc.cadn.net.cn

5.6.1. SoapFaultMappingExceptionResolver

SoapFaultMappingExceptionResolver 是一种更复杂的实现。 此解析器允许您获取可能抛出的任何异常的类名,并将其映射到 SOAP Fault:spring-doc.cadn.net.cn

<beans>
    <bean id="exceptionResolver"
        class="org.springframework.ws.soap.server.endpoint.SoapFaultMappingExceptionResolver">
        <property name="defaultFault" value="SERVER"/>
        <property name="exceptionMappings">
            <value>
                org.springframework.oxm.ValidationFailureException=CLIENT,Invalid request
            </value>
        </property>
    </bean>
</beans>

关键值和默认端点使用faultCode,faultString,locale的格式,其中只需要故障代码。 如果未设置故障字符串,则默认为异常消息。 如果未设置语言,则默认为英语。 前面的配置将类型为ValidationFailureException的异常映射到客户端SOAP错误,故障字符串为Invalid request,如下所示:spring-doc.cadn.net.cn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Client</faultcode>
           <faultstring>Invalid request</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

如果发生任何其他异常,则返回默认错误:一个以异常消息作为错误字符串的服务器端错误。spring-doc.cadn.net.cn

5.6.2. 使用 SoapFaultAnnotationExceptionResolver

你还可以使用 @SoapFault 注解来标注异常类,以指示每当抛出该异常时应返回的 SOAP 错误。 为了使这些注解生效,你需要将 SoapFaultAnnotationExceptionResolver 添加到你的应用程序上下文中。注解的元素包括错误代码枚举、错误字符串或原因以及语言。 以下示例展示了这样的异常:spring-doc.cadn.net.cn

package samples;

import org.springframework.ws.soap.server.endpoint.annotation.FaultCode;
import org.springframework.ws.soap.server.endpoint.annotation.SoapFault;

@SoapFault(faultCode = FaultCode.SERVER)
public class MyBusinessException extends Exception {

    public MyClientException(String message) {
        super(message);
    }
}

每当在端点调用期间使用构造函数字符串 "Oops!" 抛出 MyBusinessException 时,会导致以下响应:spring-doc.cadn.net.cn

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
    <SOAP-ENV:Body>
       <SOAP-ENV:Fault>
           <faultcode>SOAP-ENV:Server</faultcode>
           <faultstring>Oops!</faultstring>
       </SOAP-ENV:Fault>
    </SOAP-ENV:Body>
</SOAP-ENV:Envelope>

5.7. 服务端测试

当涉及到测试您的Web服务端点时,您有两种可能的方法:spring-doc.cadn.net.cn

  • 编写单元测试,您可以在其中为您的端点提供(模拟)参数以供其使用。 这种方法的优点是实现起来相当容易(尤其是对于带有 @Endpoint 注解的类)。 缺点是您并未真正测试通过网络发送的 XML 消息的确切内容。spring-doc.cadn.net.cn

  • 编写集成测试,以测试消息的内容。spring-doc.cadn.net.cn

第一种方法可以轻松地通过使用 Mockito、EasyMock 等模拟框架来实现。 下一节将重点介绍如何编写集成测试。spring-doc.cadn.net.cn

5.7.1. 编写服务器端集成测试

Spring-WS 支持创建端点集成测试。 在此上下文中,端点是指处理(SOAP)消息的类(参见 端点)。spring-doc.cadn.net.cn

集成测试支持位于org.springframework.ws.test.server包中。 该包中的核心类是MockWebServiceClient。 其基本思想是,此客户端创建一个请求消息,然后将其发送到在标准MessageDispatcherServlet应用程序上下文中配置的端点(参见MessageDispatcherServlet)。 这些端点处理消息并创建响应。 然后客户端接收此响应并根据注册的期望值进行验证。spring-doc.cadn.net.cn

典型用法MockWebServiceClient是:。spring-doc.cadn.net.cn

  1. 通过调用MockWebServiceClient.createClient(ApplicationContext)MockWebServiceClient.createClient(WebServiceMessageReceiver, WebServiceMessageFactory)创建一个MockWebServiceClient实例。spring-doc.cadn.net.cn

  2. 通过调用 sendRequest(RequestCreator) 发送请求消息,可以使用 RequestCreators 中提供的默认 RequestCreator 实现(可以静态导入)。spring-doc.cadn.net.cn

  3. 通过调用 andExpect(ResponseMatcher) 来设置响应期望,可能会使用 ResponseMatchers 中提供的默认 ResponseMatcher 实现(可以静态导入)。 通过链式调用 andExpect(ResponseMatcher) 可以设置多个期望。spring-doc.cadn.net.cn

MockWebServiceClient(及其相关类)提供了一个“流畅”的 API,因此你可以通常使用 IDE 中的代码补全功能来引导你完成设置模拟服务器的过程。spring-doc.cadn.net.cn

你可以在单元测试中依赖 Spring-WS 提供的标准日志记录功能。 有时,检查请求或响应消息以找出某个测试失败的原因可能会很有用。 更多信息请参见消息日志记录与跟踪spring-doc.cadn.net.cn

例如,考虑以下 web 服务端点类:spring-doc.cadn.net.cn

import org.springframework.ws.server.endpoint.annotation.Endpoint;
import org.springframework.ws.server.endpoint.annotation.RequestPayload;
import org.springframework.ws.server.endpoint.annotation.ResponsePayload;

@Endpoint                                                                (1)
public class CustomerEndpoint {

  @ResponsePayload                                                       (2)
  public CustomerCountResponse getCustomerCount(
      @RequestPayload CustomerCountRequest request) {
    CustomerCountResponse response = new CustomerCountResponse();
    response.setCustomerCount(10);
    return response;
  }

}
1 @Endpoint 注解的 CustomerEndpoint。 参见 端点
2 The getCustomerCount() 方法接收一个 CustomerCountRequest 作为参数并返回一个 CustomerCountResponse。 这两个类都是由编组器支持的对象。 例如,它们可以具有 @XmlRootElement 注解以获得 JAXB2 的支持。

以下示例显示了针对 CustomerEndpoint 的典型测试:spring-doc.cadn.net.cn

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.springframework.ws.test.server.MockWebServiceClient;
import static org.springframework.ws.test.server.RequestCreators.*;
import static org.springframework.ws.test.server.ResponseMatchers.*;

@RunWith(SpringJUnit4ClassRunner.class)                                               (1)
@ContextConfiguration("spring-ws-servlet.xml")
public class CustomerEndpointIntegrationTest {

  @Autowired
  private ApplicationContext applicationContext;                                      (2)

  private MockWebServiceClient mockClient;

  @Before
  public void createClient() {
    mockClient = MockWebServiceClient.createClient(applicationContext);               (3)
  }

  @Test
  public void customerEndpoint() throws Exception {
    Source requestPayload = new StringSource("""
      <customerCountRequest xmlns='http://springframework.org/spring-ws'>
        <customerName>John Doe</customerName>
      </customerCountRequest>
    """);
    Source responsePayload = new StringSource("""
      <customerCountResponse xmlns='http://springframework.org/spring-ws'>
        <customerCount>10</customerCount>
      </customerCountResponse>
    """);

    mockClient.sendRequest(withPayload(requestPayload)).                              (4)
      andExpect(payload(responsePayload));
  }
}
1 此测试使用 Spring 框架中提供的标准测试工具。 这种方式不是必需的,但通常是最简单的测试设置方法。
2 应用程序上下文是一个标准的 Spring-WS 应用程序上下文(参见 MessageDispatcherServlet),从 spring-ws-servlet.xml 中读取。 在这种情况下,应用程序上下文包含一个针对 CustomerEndpoint 的 bean 定义(或者可能使用了 <context:component-scan />)。
3 @Before方法中,我们通过使用createClient工厂方法创建了一个MockWebServiceClient
4 我们通过调用sendRequest()并使用静态导入的RequestCreators提供的withPayload()RequestCreator来发送请求(参见使用RequestCreatorRequestCreators)。 我们还通过调用andExpect()并使用静态导入的ResponseMatchers提供的payload()ResponseMatcher来设置响应期望(参见使用ResponseMatcherResponseMatchers)。

这部分测试可能看起来有点令人困惑,但您的 IDE 的代码补全功能会很有帮助。 在输入 sendRequest( 后,您的 IDE 可以为您提供一系列可能的请求创建策略列表,前提是您静态导入了 RequestCreators。 同样的情况也适用于 andExpect(),前提是您静态导入了 ResponseMatchersspring-doc.cadn.net.cn

5.7.2. 使用 RequestCreatorRequestCreators

最初,MockWebServiceClient需要为端点创建一个请求消息以供消费。 客户端为此目的使用RequestCreator策略接口:spring-doc.cadn.net.cn

public interface RequestCreator {

  WebServiceMessage createRequest(WebServiceMessageFactory messageFactory)
    throws IOException;

}

你可以自己编写此接口的实现,通过使用消息工厂来创建请求消息,但你绝对不是必须这样做。 RequestCreators 类提供了一种方法,可以在 withPayload() 方法中基于给定的有效负载创建 RequestCreator。 你通常会静态导入 RequestCreatorsspring-doc.cadn.net.cn

5.7.3. 使用 ResponseMatcherResponseMatchers

当请求消息已被端点处理并收到响应时,MockWebServiceClient 可以验证此响应消息是否符合某些预期。 客户端为此目的使用 ResponseMatcher 策略接口:spring-doc.cadn.net.cn

public interface ResponseMatcher {

    void match(WebServiceMessage request, WebServiceMessage response)
      throws IOException, AssertionError;

}

再次强调,你可以编写自己的接口实现,在消息不符合预期时抛出 AssertionError 实例,但你完全不必这样做,因为 ResponseMatchers 类为你提供了标准的 ResponseMatcher 实现,可以直接在测试中使用。 你通常会静态导入这个类。spring-doc.cadn.net.cn

ResponseMatchers 类提供了以下响应匹配器:spring-doc.cadn.net.cn

ResponseMatchers 方法 描述

payload()spring-doc.cadn.net.cn

期望得到一个特定的响应内容。 可能包含 XMLUnit占位符spring-doc.cadn.net.cn

validPayload()spring-doc.cadn.net.cn

期望响应内容根据给定的 XSD 模式进行验证。spring-doc.cadn.net.cn

xpath()spring-doc.cadn.net.cn

期望某个给定的 XPath 表达式存在、不存在或评估为给定值。spring-doc.cadn.net.cn

soapHeader()spring-doc.cadn.net.cn

期望在响应消息中存在给定的 SOAP 头。spring-doc.cadn.net.cn

soapEnvelope()spring-doc.cadn.net.cn

期望给定的SOAP负载。 可能包含XMLUnit占位符spring-doc.cadn.net.cn

noFault()spring-doc.cadn.net.cn

期望响应消息不包含 SOAP 错误。spring-doc.cadn.net.cn

mustUnderstandFault()clientOrSenderFault()serverOrReceiverFault()versionMismatchFault()spring-doc.cadn.net.cn

期望响应消息包含特定的 SOAP 错误。spring-doc.cadn.net.cn

您可以通过链接 andExpect() 调用来设置多个响应期望:spring-doc.cadn.net.cn

mockClient.sendRequest(...).
 andExpect(payload(expectedResponsePayload)).
 andExpect(validPayload(schemaResource));

有关 ResponseMatchers 提供的响应匹配器的更多信息,请参见 Javadocspring-doc.cadn.net.cn

6. 在客户端使用 Spring-WS

Spring-WS 提供了一个客户端的 Web 服务 API,允许以一致的、基于 XML 的方式访问 Web 服务。 它还支持使用编组器和解组器,以便您的服务层代码可以完全专注于处理 Java 对象。spring-doc.cadn.net.cn

org.springframework.ws.client.core 包提供了使用客户端访问 API 的核心功能。 它包含简化 Web 服务使用的模板类,类似于核心 Spring JdbcTemplate 对 JDBC 所做的。 Spring 模板类的共同设计原则是提供用于执行常见操作的辅助方法,并且对于更复杂的用法,委托给用户实现的回调接口。 Web 服务模板遵循相同的设计。 这些类提供了各种便捷方法用于spring-doc.cadn.net.cn

6.1. 使用客户端 API

本节描述如何使用客户端 API。 有关如何使用服务器端 API,请参见使用 Spring-WS 创建 Web 服务spring-doc.cadn.net.cn

6.1.1. WebServiceTemplate

WebServiceTemplate 是 Spring-WS 中用于客户端 Web 服务访问的核心类。 它包含发送 Source 对象并接收响应消息的方法,响应消息可以是 SourceResult。 此外,它还可以在通过传输发送之前将对象编组为 XML,并将任何响应 XML 反编组为对象。spring-doc.cadn.net.cn

URIs 和传输

WebServiceTemplate 类使用 URI 作为消息目的地。 您可以直接在模板上设置 defaultUri 属性,或者在调用模板上的方法时显式提供 URI。 URI 被解析为一个 WebServiceMessageSender,它负责通过传输层发送 XML 消息。 您可以通过使用 WebServiceTemplate 类的 messageSendermessageSenders 属性来设置一个或多个消息发送器。spring-doc.cadn.net.cn

HTTP 传输

有三种实现WebServiceMessageSender接口的方式可以通过HTTP发送消息。 默认的实现是HttpUrlConnectionMessageSender,它使用Java本身提供的功能。 另一种选择是使用JDK的HttpClientJdkHttpClientMessageSender,或者是使用Apache HttpClientHttpComponents5MessageSender/SimpleHttpComponents5MessageSender。 如果你需要更高级且易于使用的功能(例如身份验证、HTTP连接池等),请使用后者。spring-doc.cadn.net.cn

要使用 HTTP 传输,要么将 defaultUri 设置为类似 http://example.com/services 的值,要么为某个方法提供 uri 参数。spring-doc.cadn.net.cn

以下示例显示了如何为 HTTP 传输使用默认配置:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="defaultUri" value="http://example.com/WebService"/>
    </bean>

</beans>

以下示例展示了如何覆盖默认配置,以及如何使用 Apache HttpClient 通过 HTTP 认证进行身份验证:spring-doc.cadn.net.cn

<bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
    <constructor-arg ref="messageFactory"/>
    <property name="messageSender">
        <bean class="org.springframework.ws.transport.http.HttpComponents5MessageSender">
            <property name="credentials">
                <bean class="org.apache.hc.client5.http.auth.UsernamePasswordCredentials">
                    <constructor-arg value="john"/>
                    <constructor-arg value="secret"/>
                </bean>
            </property>
        </bean>
    </property>
    <property name="defaultUri" value="http://example.com/WebService"/>
</bean>
JMS 传输

为了通过JMS发送消息,Spring-WS提供了JmsMessageSender。 这个类利用Spring框架的功能将WebServiceMessage转换为JMS的Message,并将其通过QueueTopic发送出去,同时接收响应(如果有的话)。spring-doc.cadn.net.cn

要使用 JmsMessageSender,您需要将 defaultUriuri 参数设置为 JMS URI,该 URI 至少由 jms: 前缀和目标名称组成。JMS URI 的一些示例包括:jms:SomeQueuejms:SomeTopic?priority=3&deliveryMode=NON_PERSISTENTjms:RequestQueue?replyToName=ResponseName。有关此 URI 语法的更多信息,请参阅 JmsMessageSender 的 Javadocspring-doc.cadn.net.cn

默认情况下,JmsMessageSender 发送 JMS BytesMessage,但您可以通过在 JMS URI 上使用 messageType 参数来覆盖此行为,例如 jms:Queue?messageType=TEXT_MESSAGE。 请注意,BytesMessages 是首选类型,因为 TextMessages 不能可靠地支持附件和字符编码。spring-doc.cadn.net.cn

以下示例展示了如何将 JMS 传输与 Artemis 连接工厂结合使用:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connectionFactory" class="org.apache.activemq.artemis.jms.client.ActiveMQConnectionFactory">
		<property name="brokerURL" value="vm://localhost?broker.persistent=false"/>
	</bean>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.jms.JmsMessageSender">
                <property name="connectionFactory" ref="connectionFactory"/>
            </bean>
        </property>
        <property name="defaultUri" value="jms:RequestQueue?deliveryMode=NON_PERSISTENT"/>
    </bean>

</beans>
邮件传输

Spring-WS 还提供了一个电子邮件传输功能,您可以使用它通过 SMTP 发送 Web 服务消息,并通过 POP3 或 IMAP 检索它们。 客户端的电子邮件功能包含在 MailMessageSender 中。 此类根据请求 WebServiceMessage 创建电子邮件消息并通过 SMTP 发送。 然后等待响应消息到达传入的 POP3 或 IMAP 服务器。spring-doc.cadn.net.cn

要使用MailMessageSender,将defaultUriuri参数设置为mailto URI — 例如,mailto:[email protected]mailto:server@localhost?subject=SOAP%20Test。 确保消息发送器已正确配置了transportUri,它指示用于发送请求的服务器(通常为SMTP服务器),以及storeUri,它指示用于轮询响应的服务器(通常为POP3或IMAP服务器)。spring-doc.cadn.net.cn

以下示例展示了如何使用电子邮件传输:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.mail.MailMessageSender">
                <property name="from" value="Spring-WS SOAP Client &lt;[email protected]&gt;"/>
                <property name="transportUri" value="smtp://client:[email protected]"/>
                <property name="storeUri" value="imap://client:[email protected]/INBOX"/>
            </bean>
        </property>
        <property name="defaultUri" value="mailto:[email protected]?subject=SOAP%20Test"/>
    </bean>

</beans>
XMPP 传输

Spring-WS 还提供了一个 XMPP(Jabber)传输,您可以使用它通过 XMPP 发送和接收 Web 服务消息。 客户端的 XMPP 功能包含在 XmppMessageSender 中。 该类从请求 WebServiceMessage 创建一个 XMPP 消息并通过 XMPP 发送。 然后它会监听响应消息的到来。spring-doc.cadn.net.cn

要使用XmppMessageSender,请将defaultUriuri参数设置为xmpp URI — 例如,xmpp:[email protected]。 发送方还需要一个XMPPConnection才能工作,可以方便地通过使用org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean来创建。spring-doc.cadn.net.cn

以下示例展示了如何使用 XMPP 传输:spring-doc.cadn.net.cn

<beans>

    <bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory"/>

    <bean id="connection" class="org.springframework.ws.transport.xmpp.support.XmppConnectionFactoryBean">
        <property name="host" value="jabber.org"/>
        <property name="username" value="username"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="webServiceTemplate" class="org.springframework.ws.client.core.WebServiceTemplate">
        <constructor-arg ref="messageFactory"/>
        <property name="messageSender">
            <bean class="org.springframework.ws.transport.xmpp.XmppMessageSender">
                <property name="connection" ref="connection"/>
            </bean>
        </property>
        <property name="defaultUri" value="xmpp:[email protected]"/>
    </bean>

</beans>
消息工厂

除了消息发送器之外,WebServiceTemplate 还需要一个 Web 服务消息工厂。 对于 SOAP,有两个消息工厂:SaajSoapMessageFactoryAxiomSoapMessageFactory。 如果没有指定消息工厂(通过设置 messageFactory 属性),Spring-WS 默认使用 SaajSoapMessageFactoryspring-doc.cadn.net.cn

6.1.2. 发送和接收WebServiceMessage

WebServiceTemplate 包含许多用于发送和接收 Web 服务消息的便捷方法。 有些方法接受并返回 Source,还有一些方法返回 Result。 此外,还有一些方法可以将对象编组和解组为 XML。 以下示例向 Web 服务发送了一条简单的 XML 消息:spring-doc.cadn.net.cn

import java.io.StringReader;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.springframework.ws.WebServiceMessageFactory;
import org.springframework.ws.client.core.WebServiceTemplate;
import org.springframework.ws.transport.WebServiceMessageSender;

public class WebServiceClient {

    private static final String MESSAGE =
        "<message xmlns=\"http://tempuri.org\">Hello, Web Service World</message>";

    private final WebServiceTemplate webServiceTemplate = new WebServiceTemplate();

    public void setDefaultUri(String defaultUri) {
        webServiceTemplate.setDefaultUri(defaultUri);
    }

    // send to the configured default URI
    public void simpleSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult(source, result);
    }

    // send to an explicit URI
    public void customSendAndReceive() {
        StreamSource source = new StreamSource(new StringReader(MESSAGE));
        StreamResult result = new StreamResult(System.out);
        webServiceTemplate.sendSourceAndReceiveToResult("http://localhost:8080/AnotherWebService",
            source, result);
    }

}
<beans xmlns="http://www.springframework.org/schema/beans">

    <bean id="webServiceClient" class="com.example.WebServiceClient">
        <property name="defaultUri" value="http://localhost:8080/WebService"/>
    </bean>

</beans>

前面的示例使用WebServiceTemplate向位于http://localhost:8080/WebService的Web服务发送“Hello, World”消息(在simpleSendAndReceive()方法的情况下),并将结果写入控制台。 WebServiceTemplate被注入默认的URI,之所以使用默认URI是因为Java代码中没有显式提供URI。spring-doc.cadn.net.cn

请注意,WebServiceTemplate 类在配置完成后是线程安全的(假设其所有依赖项也是线程安全的,Spring-WS 提供的所有依赖项均满足此条件),因此多个对象可以共享同一个 WebServiceTemplate 实例。 WebServiceTemplate 暴露了一个无参构造函数以及 messageFactorymessageSender 两个 bean 属性,您可以使用它们来构造实例(通过 Spring 容器或普通的 Java 代码)。 或者,您可以考虑继承 Spring-WS 提供的 WebServiceGatewaySupport 便捷基类,该类暴露了 bean 属性以方便配置。 (您不必扩展这个基类,它仅作为一个便捷类提供)。spring-doc.cadn.net.cn

6.1.3. 发送和接收POJO — 编组与解组

为了方便发送普通的 Java 对象,WebServiceTemplate 提供了许多 send(..) 方法,这些方法将 Object 作为消息数据内容的参数。 WebServiceTemplate 类中的 marshalSendAndReceive(..) 方法将请求对象转换为 XML 的工作委托给 Marshaller,并将响应 XML 转换为对象的工作委托给 Unmarshaller。 (有关编组和解组的更多信息,请参阅 Spring Framework 参考文档)。spring-doc.cadn.net.cn

通过使用编组器,您的应用程序代码可以专注于正在发送或接收的业务对象,而不必关心它如何以 XML 形式表示的细节。 要使用编组功能,您必须通过WebServiceTemplate类的marshallerunmarshaller属性设置一个编组器和一个解组器。spring-doc.cadn.net.cn

6.1.4. 使用 WebServiceMessageCallback

为了适应在消息上设置 SOAP 头部以及其他配置,WebServiceMessageCallback 接口允许您在消息创建之后、发送之前访问该消息。 以下示例演示了如何在通过编组对象创建的消息上设置 SOAP 操作头部:spring-doc.cadn.net.cn

public void marshalWithSoapActionHeader(MyObject o) {

    webServiceTemplate.marshalSendAndReceive(o, new WebServiceMessageCallback() {

        public void doWithMessage(WebServiceMessage message) {
            ((SoapMessage)message).setSoapAction("http://tempuri.org/Action");
        }
    });
}
请注意,您还可以使用 org.springframework.ws.soap.client.core.SoapActionCallback 来设置 SOAP 操作头。
WS-Addressing

除了对服务器端 WS-Addressing的支持外,Spring-WS 在客户端也对该规范提供了支持。spring-doc.cadn.net.cn

要设置客户端上的 WS-Addressing 标头,您可以使用 ActionCallback。 此回调将所需的操作标头作为参数。 它还具有用于指定 WS-Addressing 版本和 To 标头的构造函数。 如果未指定,To 标头默认为正在建立的连接的 URL。spring-doc.cadn.net.cn

以下示例将Action标头设置为http://samples/RequestOrderspring-doc.cadn.net.cn

webServiceTemplate.marshalSendAndReceive(o, new ActionCallback("http://samples/RequestOrder"));

6.1.5. 使用 WebServiceMessageExtractor

WebServiceMessageExtractor 接口是一个底层回调接口,它使您能够完全控制从接收到的 Object 中提取 WebServiceMessage 的过程。 WebServiceTemplate 在底层连接到服务资源仍然打开时,调用提供的 WebServiceMessageExtractor 上的 extractData(..) 方法。 以下示例展示了 WebServiceMessageExtractor 的实际应用:spring-doc.cadn.net.cn

public void marshalWithSoapActionHeader(final Source s) {
    final Transformer transformer = transformerFactory.newTransformer();
    webServiceTemplate.sendAndReceive(new WebServiceMessageCallback() {
        public void doWithMessage(WebServiceMessage message) {
            transformer.transform(s, message.getPayloadResult());
        },
        new WebServiceMessageExtractor() {
            public Object extractData(WebServiceMessage message) throws IOException {
                // do your own transforms with message.getPayloadResult()
                // or message.getPayloadSource()
            }
          }
        });
}

6.2. 客户端测试

当涉及到测试您的Web服务客户端(即,使用WebServiceTemplate访问Web服务的类)时,您有两种可能的方法:spring-doc.cadn.net.cn

  • 编写单元测试,模拟掉WebServiceTemplate类、WebServiceOperations接口,或者整个客户端类。 这种方法的优点是容易实现。 缺点是你并没有真正测试通过网络发送的XML消息的确切内容,特别是当模拟整个客户端类时。spring-doc.cadn.net.cn

  • 编写集成测试,以测试消息的内容。spring-doc.cadn.net.cn

第一种方法可以很容易地通过使用诸如 Mockito、EasyMock 等模拟框架来实现。 下一节将重点介绍如何编写集成测试。spring-doc.cadn.net.cn

6.2.1. 编写客户端集成测试

Spring-WS 支持创建 Web 服务客户端集成测试。 在此上下文中,客户端是指使用 WebServiceTemplate 访问 Web 服务的类。spring-doc.cadn.net.cn

集成测试支持位于org.springframework.ws.test.client包中。 该包中的核心类是MockWebServiceServer。 其基本思想是,Web服务模板连接到此模拟服务器并向其发送请求消息,然后模拟服务器根据注册的期望验证该请求消息。 如果期望得到满足,模拟服务器将准备响应消息并将其发送回模板。spring-doc.cadn.net.cn

典型用法MockWebServiceServer是:。spring-doc.cadn.net.cn

  1. 通过调用 MockWebServiceServer.createServer(WebServiceTemplate)MockWebServiceServer.createServer(WebServiceGatewaySupport)MockWebServiceServer.createServer(ApplicationContext) 创建一个 MockWebServiceServer 实例。spring-doc.cadn.net.cn

  2. 通过调用 expect(RequestMatcher) 来设置请求期望,可以使用 RequestMatchers 中提供的默认 RequestMatcher 实现(可以静态导入)。 通过链式调用 andExpect(RequestMatcher) 可以设置多个期望。spring-doc.cadn.net.cn

  3. 通过调用 andRespond(ResponseCreator) 创建一个适当的响应消息,可能使用 ResponseCreators 中提供的默认 ResponseCreator 实现(可以静态导入)。spring-doc.cadn.net.cn

  4. 像平常一样使用WebServiceTemplate,可以直接使用或通过客户端代码使用。spring-doc.cadn.net.cn

  5. 调用 MockWebServiceServer.verify() 以确保所有预期都已满足。spring-doc.cadn.net.cn

MockWebServiceServer(及其相关类)提供了一个“流畅”的 API,因此你可以通常使用 IDE 中的代码补全功能来引导你完成设置模拟服务器的过程。spring-doc.cadn.net.cn

你可以在单元测试中依赖 Spring-WS 提供的标准日志记录功能。 有时,检查请求或响应消息以找出某个测试失败的原因可能会很有用。 更多信息请参见消息日志记录与跟踪spring-doc.cadn.net.cn

例如,考虑以下 Web 服务客户端类:spring-doc.cadn.net.cn

import org.springframework.ws.client.core.support.WebServiceGatewaySupport;

public class CustomerClient extends WebServiceGatewaySupport {                          (1)

  public int getCustomerCount() {
    CustomerCountRequest request = new CustomerCountRequest();                          (2)
    request.setCustomerName("John Doe");
    CustomerCountResponse response =
      (CustomerCountResponse) getWebServiceTemplate().marshalSendAndReceive(request);   (3)
    return response.getCustomerCount();
  }

}
1 CustomerClient扩展了WebServiceGatewaySupport,这使其具有一个webServiceTemplate属性。
2 CustomerCountRequest 是一个由编组器支持的对象。 例如,它可以具有 @XmlRootElement 注解以被 JAXB2 支持。
3 CustomerClient 使用 WebServiceGatewaySupport 提供的 WebServiceTemplate 将请求对象编组为 SOAP 消息,并将其发送到 Web 服务。 响应对象被解组为 CustomerCountResponse

以下示例显示了针对 CustomerClient 的典型测试:spring-doc.cadn.net.cn

import javax.xml.transform.Source;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.xml.transform.StringSource;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.assertEquals;

import org.springframework.ws.test.client.MockWebServiceServer;
import static org.springframework.ws.test.client.RequestMatchers.*;
import static org.springframework.ws.test.client.ResponseCreators.*;

@RunWith(SpringJUnit4ClassRunner.class)                                                 (1)
@ContextConfiguration("integration-test.xml")
public class CustomerClientIntegrationTest {

  @Autowired
  private CustomerClient client;                                                        (2)

  private MockWebServiceServer mockServer;                                              (3)

  @Before
  public void createServer() throws Exception {
    mockServer = MockWebServiceServer.createServer(client);
  }

  @Test
  public void customerClient() throws Exception {
    Source requestPayload = new StringSource("""
      <customerCountRequest xmlns='http://springframework.org/spring-ws'>
        <customerName>John Doe</customerName>
      </customerCountRequest>
    """);
    Source responsePayload = new StringSource("""
      <customerCountResponse xmlns='http://springframework.org/spring-ws'>
        <customerCount>10</customerCount>
      </customerCountResponse>
    """);

    mockServer.expect(payload(requestPayload)).andRespond(withPayload(responsePayload));(4)

    int result = client.getCustomerCount();                                             (5)
    assertEquals(10, result);

    mockServer.verify();                                                                (6)
  }

}
1 此测试使用 Spring 框架中提供的标准测试工具。 这种方式不是必需的,但通常是最简单的测试设置方法。
2 CustomerClient 配置在 integration-test.xml 中,并通过 @Autowired 装配到此测试中。
3 @Before方法中,我们通过使用createServer工厂方法创建了一个MockWebServiceServer
4 我们通过调用 expect() 并使用静态导入的 RequestMatchers 提供的 payload() RequestMatcher 来定义期望(参见 使用 RequestMatcherRequestMatchers)。 我们还通过调用 andRespond() 并使用静态导入的 ResponseCreators 提供的 withPayload() ResponseCreator 来设置响应(参见 使用 ResponseCreatorResponseCreators)。 这部分测试可能看起来有点令人困惑,但您的 IDE 的代码补全功能会提供很大帮助。 在您输入 expect( 后,如果静态导入了 RequestMatchers,您的 IDE 可以为您提供一系列可能的请求匹配策略。 同样的规则也适用于 andRespond(,前提是您静态导入了 ResponseCreators
5 我们对CustomerClient调用了getCustomerCount(),因此使用了WebServiceTemplate。 到目前为止,模板已经设置为“测试模式”,所以这个方法调用不会建立真实的(HTTP)连接。 我们还根据方法调用的结果做了一些JUnit断言。
6 我们对MockWebServiceServer调用verify(),验证确实收到了预期的消息。

6.2.2. 使用 RequestMatcherRequestMatchers

为了验证请求消息是否符合某些预期,MockWebServiceServer 使用了 RequestMatcher 策略接口。 该接口定义的契约如下:spring-doc.cadn.net.cn

public interface RequestMatcher {

  void match(URI uri, WebServiceMessage request)
    throws IOException, AssertionError;

}

你可以编写自己的接口实现,在消息不符合预期时抛出AssertionError异常,但你肯定不是必须这样做。 RequestMatchers类为你提供了标准的RequestMatcher实现,供你在测试中使用。 你通常会静态导入这个类。spring-doc.cadn.net.cn

RequestMatchers 类提供了以下请求匹配器:spring-doc.cadn.net.cn

RequestMatchers 方法 描述

anything()spring-doc.cadn.net.cn

期望任何形式的请求。spring-doc.cadn.net.cn

payload()spring-doc.cadn.net.cn

期望给定的请求负载。 可能包含 XMLUnit占位符spring-doc.cadn.net.cn

validPayload()spring-doc.cadn.net.cn

期望请求负载根据给定的 XSD 模式进行验证。spring-doc.cadn.net.cn

xpath()spring-doc.cadn.net.cn

期望某个给定的 XPath 表达式存在、不存在或评估为给定值。spring-doc.cadn.net.cn

soapHeader()spring-doc.cadn.net.cn

期望请求消息中存在给定的 SOAP 头。spring-doc.cadn.net.cn

soapEnvelope()spring-doc.cadn.net.cn

期望给定的SOAP负载。 可能包含XMLUnit占位符spring-doc.cadn.net.cn

connectionTo()spring-doc.cadn.net.cn

期望连接到给定的URL。spring-doc.cadn.net.cn

你可以通过链式调用 andExpect() 来设置多个请求期望:spring-doc.cadn.net.cn

mockServer.expect(connectionTo("http://example.com")).
 andExpect(payload(expectedRequestPayload)).
 andExpect(validPayload(schemaResource)).
 andRespond(...);

有关 RequestMatchers 提供的请求匹配器的更多信息,请参见 Javadocspring-doc.cadn.net.cn

6.2.3. 使用 ResponseCreatorResponseCreators

当请求消息经过验证并符合定义的预期时,MockWebServiceServer 会为 WebServiceTemplate 创建一个响应消息以供其使用。 服务器为此目的使用 ResponseCreator 策略接口:spring-doc.cadn.net.cn

public interface ResponseCreator {

  WebServiceMessage createResponse(URI uri, WebServiceMessage request,
                                   WebServiceMessageFactory messageFactory)
    throws IOException;

}

再次,您可以自己编写此接口的实现,通过使用消息工厂创建响应消息,但您完全不必这样做,因为ResponseCreators类为您提供了标准的ResponseCreator实现,供您在测试中使用。 您通常会静态导入此类。spring-doc.cadn.net.cn

ResponseCreators 类提供了以下响应:spring-doc.cadn.net.cn

ResponseCreators 方法 描述

withPayload()spring-doc.cadn.net.cn

创建一个具有给定有效负载的响应消息。spring-doc.cadn.net.cn

withError()spring-doc.cadn.net.cn

在响应连接中创建一个错误。 此方法为您提供测试错误处理的机会。spring-doc.cadn.net.cn

withException()spring-doc.cadn.net.cn

从响应连接读取时抛出异常。 此方法为您提供测试异常处理的机会。spring-doc.cadn.net.cn

withMustUnderstandFault(), withClientOrSenderFault(), withServerOrReceiverFault(), or withVersionMismatchFault()spring-doc.cadn.net.cn

使用给定的 SOAP 错误创建响应消息。此方法为您提供测试错误处理的机会。spring-doc.cadn.net.cn

有关 RequestMatchers 提供的请求匹配器的更多信息,请参见 Javadocspring-doc.cadn.net.cn

7. 使用 Spring-WS 保护您的 Web 服务

本章解释如何为您的Web服务添加WS-Security相关内容。 我们重点关注WS-Security的三个不同领域:spring-doc.cadn.net.cn

  • 认证: 这是确定主体是否是他们所声称的身份的过程。 在此上下文中,“主体”通常指用户、设备或某些其他可以在您的应用程序中执行操作的系统。spring-doc.cadn.net.cn

  • 数字签名: 消息的数字签名是基于文档和签名者私钥生成的一段信息。 它是通过使用哈希函数和私有签名函数(使用签名者的私钥加密)创建的。spring-doc.cadn.net.cn

  • 加密与解密: 加密是将数据转换为在没有适当密钥的情况下无法读取的形式的过程。 它主要用于对信息进行保密,不让任何未授权的人读取。 解密是加密的逆过程。 它是将加密的数据转换回可读形式的过程。spring-doc.cadn.net.cn

WS-Security(尤其是加密和签名)需要大量的内存,可能会降低性能。 如果性能对您很重要,您可以考虑不使用 WS-Security 或使用基于 HTTP 的安全性。spring-doc.cadn.net.cn

Wss4jSecurityInterceptor 是一个基于 Apache 的 WSS4JEndpointInterceptor(参见 拦截请求 —— EndpointInterceptor 接口)。spring-doc.cadn.net.cn

WSS4J 实现了以下标准:spring-doc.cadn.net.cn

此拦截器支持由AxiomSoapMessageFactorySaajSoapMessageFactory创建的消息。spring-doc.cadn.net.cn

7.1. 构建配置

WSS4J 支持基于 OpenSAML 库构建,该库需要额外的仓库配置。spring-doc.cadn.net.cn

7.1.1. 使用 Maven

使用 Maven 时,您需要在 POM 文件中添加一个额外的 repository 元素,如下所示:spring-doc.cadn.net.cn

<repositories>
	<repository>
		<id>shibboleth-releases</id>
		<name>Shibboleth Releases Repository</name>
		<url>https://build.shibboleth.net/maven/releases</url>
		<snapshots>
			<enabled>false</enabled>
		</snapshots>
	</repository>
</repositories>

7.1.2. 使用 Gradle

在 Gradle 中,应在构建脚本中添加一个 repository 元素:spring-doc.cadn.net.cn

repositories {
    maven { url "https://build.shibboleth.net/maven/releases" }
}

7.2. 配置Wss4jSecurityInterceptor

WSS4J 不使用外部配置文件。 拦截器完全通过属性进行配置。 此拦截器调用的验证和保护操作分别通过 validationActionssecurementActions 属性指定。 操作以空格分隔的字符串形式传递。 以下列表显示了一个示例配置:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="UsernameToken Encrypt"/>
    ...
    <property name="securementActions" value="Encrypt"/>
    ...
</bean>

下表显示了可用的验证操作:spring-doc.cadn.net.cn

验证操作 描述

UsernameTokenspring-doc.cadn.net.cn

验证用户名Tokensspring-doc.cadn.net.cn

Timestampspring-doc.cadn.net.cn

验证时间戳spring-doc.cadn.net.cn

Encryptspring-doc.cadn.net.cn

解密消息spring-doc.cadn.net.cn

Signaturespring-doc.cadn.net.cn

验证签名spring-doc.cadn.net.cn

NoSecurityspring-doc.cadn.net.cn

未执行任何操作spring-doc.cadn.net.cn

下表显示了可用的安全操作:spring-doc.cadn.net.cn

安全措施 描述

UsernameTokenspring-doc.cadn.net.cn

添加用户名Tokensspring-doc.cadn.net.cn

UsernameTokenSignaturespring-doc.cadn.net.cn

添加用户名Tokens和签名用户名Tokens密钥spring-doc.cadn.net.cn

Timestampspring-doc.cadn.net.cn

添加时间戳spring-doc.cadn.net.cn

Encryptspring-doc.cadn.net.cn

加密响应spring-doc.cadn.net.cn

Signaturespring-doc.cadn.net.cn

签名响应spring-doc.cadn.net.cn

NoSecurityspring-doc.cadn.net.cn

未执行任何操作spring-doc.cadn.net.cn

操作的顺序非常重要,并由拦截器强制执行。 如果其安全操作的执行顺序与validationActions指定的顺序不同,拦截器将拒绝传入的SOAP消息。spring-doc.cadn.net.cn

7.3. 处理数字证书

对于需要与密钥库或证书处理进行交互的加密操作(如签名、加密和解密操作),WSS4J 需要一个 org.apache.ws.security.components.crypto.Crypto 的实例。spring-doc.cadn.net.cn

Crypto 个实例可以从 WSS4J 的 CryptoFactory 获取,或者更方便地通过 Spring-WS 的 CryptoFactoryBean 获取。spring-doc.cadn.net.cn

7.3.1. CryptoFactoryBean

Spring-WS 提供了一个便捷的工厂 bean,CryptoFactoryBean,它通过强类型属性(推荐)或通过Properties对象来构造和配置Crypto实例。spring-doc.cadn.net.cn

默认情况下,CryptoFactoryBean 返回 org.apache.ws.security.components.crypto.Merlin 的实例。 您可以通过设置 cryptoProvider 属性(或其等效的 org.apache.ws.security.crypto.provider 字符串属性)来更改此行为。spring-doc.cadn.net.cn

以下示例配置使用 CryptoFactoryBeanspring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
    <property name="keyStorePassword" value="mypassword"/>
    <property name="keyStoreLocation" value="file:/path_to_keystore/keystore.jks"/>
</bean>

7.4. 认证

本节讨论如何使用 Wss4jSecurityInterceptor 进行身份验证。spring-doc.cadn.net.cn

7.4.1. 验证用户名Tokens

Spring-WS 提供了一组回调处理器,用于与 Spring Security 集成。 此外,还提供了一个简单的回调处理器,SimplePasswordValidationCallbackHandler,用于通过内存中的 Properties 对象配置用户和密码。spring-doc.cadn.net.cn

回调处理程序是通过 Wss4jSecurityInterceptor 属性的 validationCallbackHandler 配置的。spring-doc.cadn.net.cn

7.4.2. 使用 SimplePasswordValidationCallbackHandler

SimplePasswordValidationCallbackHandler 验证明文和摘要形式的用户名Tokens,并与内存中的 Properties 对象进行比对。 您可以按以下方式对其进行配置:spring-doc.cadn.net.cn

<bean id="callbackHandler"
    class="org.springframework.ws.soap.security.wss4j2.callback.SimplePasswordValidationCallbackHandler">
    <property name="users">
        <props>
            <prop key="Bert">Ernie</prop>
        </props>
    </property>
</bean>
使用SpringSecurityPasswordValidationCallbackHandler

SpringSecurityPasswordValidationCallbackHandler 通过使用 Spring Security 的 UserDetailService 来验证纯文本和摘要密码。 它使用此服务检索Tokens中指定用户的密码(或密码的摘要)。 然后将此详细信息对象中包含的密码(或密码的摘要)与消息中的摘要进行比较。 如果它们相等,则用户已成功通过身份验证,并且会将 UsernamePasswordAuthenticationToken 存储在 SecurityContextHolder 中。 您可以使用 userDetailsService 设置该服务。 此外,您可以设置一个 userCache 属性以缓存加载的用户详细信息,如下所示:spring-doc.cadn.net.cn

<beans>
    <bean class="org.springframework.ws.soap.security.wss4j2.callback.SpringDigestPasswordValidationCallbackHandler">
        <property name="userDetailsService" ref="userDetailsService"/>
    </bean>

    <bean id="userDetailsService" class="com.mycompany.app.dao.UserDetailService" />
    ...
</beans>

7.4.3. 添加用户名Tokens

将用户名Tokens添加到传出消息就像在 Wss4jSecurityInterceptorsecurementActions 属性中添加 UsernameToken 并指定 securementUsernamesecurementPassword 一样简单。spring-doc.cadn.net.cn

可以通过设置securementPasswordType属性来设置密码类型。 可能的值是PasswordText表示明文密码,或PasswordDigest表示摘要密码,默认值为摘要密码。spring-doc.cadn.net.cn

以下示例生成带有摘要密码的用户名Tokens:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
</bean>

如果选择了纯文本密码类型,则可以通过设置securementUsernameTokenElements属性来指示拦截器添加NonceCreated元素。 该值必须是一个包含所需元素名称的列表,名称之间用空格分隔(区分大小写)。spring-doc.cadn.net.cn

以下示例生成一个带有明文密码的用户名Tokens,一个 Nonce 元素和一个 Created 元素:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="UsernameToken"/>
    <property name="securementUsername" value="Ernie"/>
    <property name="securementPassword" value="Bert"/>
    <property name="securementPasswordType" value="PasswordText"/>
    <property name="securementUsernameTokenElements" value="Nonce Created"/>
</bean>

7.4.4. 证书认证

由于证书认证类似于数字签名,WSS4J 将其作为签名验证和安全保障的一部分来处理。 具体来说,必须将 securementSignatureKeyIdentifier 属性设置为 DirectReference,以指示 WSS4J 生成包含 X509 证书的 BinarySecurityToken 元素,并将其包含在传出消息中。 证书的名称和密码分别通过 securementUsernamesecurementPassword 属性传递,如下例所示:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Signature"/>
    <property name="securementSignatureKeyIdentifier" value="DirectReference"/>
    <property name="securementUsername" value="mycert"/>
    <property name="securementPassword" value="certpass"/>
    <property name="securementSignatureCrypto">
      <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
        <property name="keyStorePassword" value="123456"/>
        <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
      </bean>
    </property>
</bean>

对于证书验证,适用常规签名验证:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
      <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
        <property name="keyStorePassword" value="123456"/>
        <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
      </bean>
    </property>
</bean>

在验证结束时,拦截器通过委托给默认的 WSS4J 实现自动验证证书的有效性。 如果需要,您可以通过重新定义 verifyCertificateTrust 方法来更改此行为。spring-doc.cadn.net.cn

有关更多详细信息,请参阅数字签名spring-doc.cadn.net.cn

7.5. 安全时间戳

本节描述了Wss4jSecurityInterceptor中可用的各种时间戳选项。spring-doc.cadn.net.cn

7.5.1. 验证时间戳

为了验证时间戳,请将 Timestamp 添加到 validationActions 属性中。 您可以通过将 timestampStrict 设置为 true 来覆盖由 SOAP 消息发起者指定的时间戳语义,并通过设置 timeToLive 属性来指定服务器端的生存时间(以秒为单位,默认值:300)。 无论 timeToLive 的值是什么,拦截器始终会拒绝已过期的时间戳。spring-doc.cadn.net.cn

在以下示例中,拦截器将时间戳的有效窗口限制为10秒,拒绝该窗口之外的任何有效时间戳Tokens:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Timestamp"/>
    <property name="timestampStrict" value="true"/>
    <property name="timeToLive" value="10"/>
</bean>

7.5.2. 添加时间戳

Timestamp添加到securementActions属性会在传出消息中生成时间戳头。 timestampPrecisionInMilliseconds属性指定生成的时间戳的精度是否为毫秒级。 默认值是true。 以下清单添加了一个时间戳:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Timestamp"/>
    <property name="timestampPrecisionInMilliseconds" value="true"/>
</bean>

7.6. 数字签名

本节描述了Wss4jSecurityInterceptor中可用的各种签名选项。spring-doc.cadn.net.cn

7.6.1. 验证签名

为了指示Wss4jSecurityInterceptorvalidationActions必须包含Signature操作。 此外,validationSignatureCrypto属性必须指向包含启动者公共证书的密钥库:spring-doc.cadn.net.cn

<bean id="wsSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>

7.6.2. 签名消息

通过将 Signature 操作添加到 securementActions 中,可以启用对传出消息的签名。 要使用的私钥的别名和密码分别由 securementUsernamesecurementPassword 属性指定。 securementSignatureCrypto 必须指向包含私钥的密钥库:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Signature"/>
    <property name="securementUsername" value="mykey"/>
    <property name="securementPassword" value="123456"/>
    <property name="securementSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
</bean>

此外,您可以通过设置 securementSignatureAlgorithm 属性来定义签名算法。spring-doc.cadn.net.cn

您可以通过设置securementSignatureKeyIdentifier属性来自定义要使用的键标识符类型。 只有IssuerSerialDirectReference对签名有效。spring-doc.cadn.net.cn

securementSignatureParts 属性控制消息的哪一部分被签名。 此属性的值是一个以分号分隔的元素名称列表,用于标识要签名的元素。 签名部分的一般形式为 {}{namespace}Element。 请注意,第一组空括号仅用于加密部分。 默认行为是对 SOAP 主体进行签名。spring-doc.cadn.net.cn

以下示例展示了如何在 Spring-WS 回声示例中对 echoResponse 元素进行签名:spring-doc.cadn.net.cn

<property name="securementSignatureParts"
    value="{}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>

要指定一个没有命名空间的元素,请使用字符串 Null(区分大小写)作为命名空间名称。spring-doc.cadn.net.cn

如果请求中的其他元素都没有本地名称为 Body,则 SOAP 命名空间标识符可以为空 ({})。spring-doc.cadn.net.cn

7.6.3. 签名确认

通过将enableSignatureConfirmation设置为true来启用签名确认。 请注意,签名确认操作跨越请求和响应。 这意味着即使没有相应的安全操作,也必须将secureResponsevalidateRequest设置为true(这是默认值)。 以下示例将enableSignatureConfirmation属性设置为truespring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Signature"/>
    <property name="enableSignatureConfirmation" value="true"/>
    <property name="validationSignatureCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="file:/keystore.jks"/>
        </bean>
    </property>
</bean>

7.7. 解密与加密

本节描述了Wss4jSecurityInterceptor中可用的各种解密和加密选项。spring-doc.cadn.net.cn

7.7.1. 解密

解密传入的 SOAP 消息需要将 Encrypt 动作添加到 validationActions 属性中。 其余的配置取决于消息中出现的密钥信息。 (这是因为 WSS4J 仅需要一个用于加密密钥的 Crypto,而嵌入式密钥名称验证则委托给回调处理程序)。spring-doc.cadn.net.cn

要解密带有嵌入式加密对称密钥的消息(xenc:EncryptedKey 元素),validationDecryptionCrypto 需要指向一个包含解密私钥的密钥库。 此外,必须为 validationCallbackHandler 注入一个 KeyStoreCallbackHandler,以指定该密钥的密码:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationDecryptionCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="classpath:/keystore.jks"/>
        </bean>
    </property>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j2.callback.KeyStoreCallbackHandler">
            <property name="privateKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>

为了支持解密带有嵌入式密钥名称的消息(ds:KeyName 元素),您可以配置一个指向包含对称密钥的密钥库的 KeyStoreCallbackHandlersymmetricKeyPassword 属性表示密钥的密码,密钥名称由 ds:KeyName 元素指定:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="validationActions" value="Encrypt"/>
    <property name="validationCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j2.callback.KeyStoreCallbackHandler">
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="classpath:keystore.jks"/>
                    <property name="type" value="JCEKS"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
            <property name="symmetricKeyPassword" value="mykeypass"/>
        </bean>
    </property>
</bean>

7.7.2. 加密

Encrypt添加到securementActions可以启用传出消息的加密功能。 您可以通过设置securementEncryptionUser属性来指定用于加密的证书别名。 证书所在的密钥库通过securementEncryptionCrypto属性进行访问。 由于加密依赖于公钥证书,因此无需传递密码。 以下示例使用了securementEncryptionCrypto属性:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Encrypt"/>
    <property name="securementEncryptionUser" value="mycert"/>
    <property name="securementEncryptionCrypto">
        <bean class="org.springframework.ws.soap.security.wss4j2.support.CryptoFactoryBean">
            <property name="keyStorePassword" value="123456"/>
            <property name="keyStoreLocation" value="file:/keystore.jks"/>
        </bean>
    </property>
</bean>

你可以通过多种方式自定义加密:使用的密钥标识符类型由securementEncryptionKeyIdentifier属性定义。 可能的值包括IssuerSerialX509KeyIdentifierDirectReferenceThumbprintSKIKeyIdentifierEmbeddedKeyNamespring-doc.cadn.net.cn

如果选择 EmbeddedKeyName 类型,则需要指定用于加密的密钥。 密钥的别名设置在 securementEncryptionUser 属性中,类似于其他密钥标识符类型。 然而,WSS4J 需要一个回调处理器来获取密钥。 因此,您必须为 securementCallbackHandler 提供一个指向适当密钥库的 KeyStoreCallbackHandler。 默认情况下,生成的 WS-Security 头中的 ds:KeyName 元素会采用 securementEncryptionUser 属性的值。 若要指定不同的名称,可以将 securementEncryptionEmbeddedKeyName 设置为所需的值。 在下一个示例中,传出消息使用别名为 secretKey 的密钥进行加密,而 myKey 出现在 ds:KeyName 元素中:spring-doc.cadn.net.cn

<bean class="org.springframework.ws.soap.security.wss4j2.Wss4jSecurityInterceptor">
    <property name="securementActions" value="Encrypt"/>
    <property name="securementEncryptionKeyIdentifier" value="EmbeddedKeyName"/>
    <property name="securementEncryptionUser" value="secretKey"/>
    <property name="securementEncryptionEmbeddedKeyName" value="myKey"/>
    <property name="securementCallbackHandler">
        <bean class="org.springframework.ws.soap.security.wss4j2.callback.KeyStoreCallbackHandler">
            <property name="symmetricKeyPassword" value="keypass"/>
            <property name="keyStore">
                <bean class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
                    <property name="location" value="file:/keystore.jks"/>
                    <property name="type" value="jceks"/>
                    <property name="password" value="123456"/>
                </bean>
            </property>
        </bean>
    </property>
</bean>

securementEncryptionKeyTransportAlgorithm 属性定义了用于加密生成的对称密钥的算法。 支持的值有 http://www.w3.org/2001/04/xmlenc#rsa-1_5,这是默认值,和 http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1pspring-doc.cadn.net.cn

你可以通过设置securementEncryptionSymAlgorithm属性来指定要使用的对称加密算法。 支持的值包括http://www.w3.org/2001/04/xmlenc#aes128-cbc(默认)、http://www.w3.org/2001/04/xmlenc#tripledes-cbchttp://www.w3.org/2001/04/xmlenc#aes256-cbchttp://www.w3.org/2001/04/xmlenc#aes192-cbcspring-doc.cadn.net.cn

最后,securementEncryptionParts 属性定义了消息的哪些部分被加密。 此属性的值是一个由分号分隔的元素名称列表,用于标识要加密的元素。 每个元素名称之前可能会带有花括号括起来的加密模式说明符和命名空间标识。 加密模式说明符可以是 {Content}{Element}。有关元素加密和内容加密之间的区别,请参阅 W3C XML 加密规范。 以下示例标识了来自 echo 示例的 echoResponsespring-doc.cadn.net.cn

<property name="securementEncryptionParts"
    value="{Content}{http://www.springframework.org/spring-ws/samples/echo}echoResponse"/>

请注意,元素名称、命名空间标识符和加密修饰符是区分大小写的。 您可以省略加密修饰符和命名空间标识符。 如果省略,加密模式默认为Content,命名空间设置为SOAP命名空间。spring-doc.cadn.net.cn

要指定一个不带命名空间的元素,请使用值 Null(区分大小写)作为命名空间名称。 如果未指定列表,处理器默认以 Content 模式加密 SOAP Body。spring-doc.cadn.net.cn

7.8. 安全异常处理

当一个安全操作或验证操作失败时,Wss4jSecurityInterceptor 会分别抛出 WsSecuritySecurementExceptionWsSecurityValidationException。 这些异常绕过了标准异常处理机制,但会被拦截器自身处理。spring-doc.cadn.net.cn

WsSecuritySecurementException 个异常由 handleSecurementException 方法的 Wss4jSecurityInterceptor 处理。 默认情况下,此方法会记录错误并停止消息的进一步处理。spring-doc.cadn.net.cn

同样,WsSecurityValidationException 异常由 handleValidationException 方法的 Wss4jSecurityInterceptor 处理。 默认情况下,此方法创建一个 SOAP 1.1 客户端或 SOAP 1.2 发送方错误,并将其作为响应发送回去。spring-doc.cadn.net.cn

handleSecurementExceptionhandleValidationException 都是受保护的方法,您可以重写它们以改变其默认行为。