在本章中,我們將探討在客戶端和伺服器端 Spring-WS 開發之間共享的元件。這些介面和類代表了 Spring-WS 的構建塊,因此理解它們的作用非常重要,即使你可能不直接使用它們。
Spring Web Services 的核心介面之一是 WebServiceMessage
。此介面代表協議無關的 XML 訊息。介面包含提供訪問訊息負載的方法,形式為 javax.xml.transform.Source
或 javax.xml.transform.Result
。Source
和 Result
是標記介面,它們表示 XML 輸入和輸出的抽象。具體實現包裝了各種 XML 表示形式,如下表所示。
Source/Result 實現 | 包裝的 XML 表示形式 |
---|---|
javax.xml.transform.dom.DOMSource | org.w3c.dom.Node |
javax.xml.transform.dom.DOMResult | org.w3c.dom.Node |
javax.xml.transform.sax.SAXSource | org.xml.sax.InputSource 和 org.xml.sax.XMLReader |
javax.xml.transform.sax.SAXResult | org.xml.sax.ContentHandler |
javax.xml.transform.stream.StreamSource |
java.io.File 、java.io.InputStream 或 java.io.Reader |
javax.xml.transform.stream.StreamResult
|
java.io.File 、java.io.OutputStream 或 java.io.Writer |
除了從負載讀取和寫入負載外,Web Service 訊息還可以將自身寫入輸出流。
SoapMessage
是 WebServiceMessage
的子類。它包含 SOAP 特有的方法,例如獲取 SOAP 頭、SOAP 錯誤等。通常,你的程式碼不應依賴於 SoapMessage
,因為 SOAP Body 的內容(即訊息的負載)可以透過 WebServiceMessage
中的 getPayloadSource()
和 getPayloadResult()
方法獲得。只有在需要執行 SOAP 特定操作(例如新增頭、獲取附件等)時,才需要將 WebServiceMessage
轉換為 SoapMessage
。
具體的訊息實現由 WebServiceMessageFactory
建立。此工廠可以建立空訊息,或基於輸入流讀取訊息。有 WebServiceMessageFactory
的兩個具體實現;一個基於 SAAJ(SOAP with Attachments API for Java),另一個基於 Axis 2 的 AXIOM(AXis Object Model)。
SaajSoapMessageFactory
使用 SOAP with Attachments API for Java 來建立 SoapMessage
實現。SAAJ是J2EE1.4 的一部分,因此它應該在大多數現代應用伺服器下受到支援。以下是SAAJ常見應用伺服器提供的版本概覽
應用伺服器 | SAAJ版本 |
---|---|
BEA WebLogic 8 | 1.1 |
BEA WebLogic 9 | 1.1/1.2[a] |
IBM WebSphere 6 | 1.2 |
SUN Glassfish 1 | 1.3 |
[a] Weblogic 9 在 1.2 實現中存在一個已知 bug:它實現了所有 1.2 介面,但在呼叫時會丟擲 |
此外,Java SE 6 包含SAAJ1.3。你可以像這樣配置一個 SaajSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.saaj.SaajSoapMessageFactory" />
SAAJSAAJ 基於 DOM(Document Object Model)。這意味著所有 SOAP 訊息都 儲存在記憶體中。對於較大的 SOAP 訊息,這可能效能不高。在這種情況下,AxiomSoapMessageFactory
可能更適用。
AxiomSoapMessageFactory
使用 AXis 2 Object Model 來建立 SoapMessage
實現。AXIOM基於StAX(Streaming API for XML)。StAX 提供了一種基於拉取(pull-based)機制來讀取 XML 訊息,這對於較大的訊息來說效率更高。
為了提高 AxiomSoapMessageFactory
的讀取效能,你可以將 payloadCaching 屬性設定為 false(預設為 true)。這將直接從 socket 流中讀取 SOAP body 的內容。啟用此設定後,負載只能讀取一次。這意味著你必須確保訊息的任何預處理(日誌記錄等)不會消耗掉它。
你可以按如下方式使用 AxiomSoapMessageFactory
<bean id="messageFactory" class="org.springframework.ws.soap.axiom.AxiomSoapMessageFactory"> <property name="payloadCaching" value="true"/> </bean>
除了負載快取,AXIOMAXIOM 還支援完整的流式訊息,如 StreamingWebServiceMessage
中所定義。這意味著負載可以直接設定在響應訊息上,而不是寫入 DOM 樹或緩衝區。
對於AXIOMAXIOM 的完整流式處理,當處理方法返回一個JAXB2支援的物件時使用。它會自動將此經過 marshal 的物件設定到響應訊息中,並在響應發出時將其寫入輸出 socket 流。
有關完整流式處理的更多資訊,請參閱 StreamingWebServiceMessage
和 StreamingPayload
的類級 Javadoc。
SaajSoapMessageFactory
和 AxiomSoapMessageFactory
都有一個 soapVersion 屬性,你可以在其中注入一個 SoapVersion
常量。預設情況下,版本是 1.1,但你可以像這樣將其設定為 1.2
<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>
在上面的示例中,我們定義了一個只接受SOAPSOAP 1.2 訊息的 SaajSoapMessageFactory
。
儘管SOAPSOAP 的兩個版本格式非常相似,但 1.2 版本與 1.1 不向後相容,因為它使用了不同的 XML 名稱空間。SOAPSOAP1.1 和 1.2 之間的其他主要區別包括 Fault 的結構不同,以及 SOAPAction
HTTP 頭實際上已被棄用(儘管它們仍然有效)。
關於SOAPSOAP 版本號,或更籠統地說,關於 WS-* 規範版本號,需要注意的一點是,規範的最新版本通常不是最流行的版本。對於SOAPSOAP,這意味著目前最好的版本是 1.1。將來 1.2 版本可能會變得更流行,但目前 1.1 是最安全的選擇。
通常,訊息成對出現:請求和響應。請求在客戶端建立,透過某種傳輸傳送到伺服器端,伺服器端生成響應。此響應被髮送回客戶端,並在客戶端讀取。
在 Spring Web Services 中,此類對話包含在 MessageContext
中,它具有獲取請求和響應訊息的屬性。在客戶端,訊息上下文由 WebServiceTemplate
建立。在伺服器端,訊息上下文從特定於傳輸的輸入流中讀取。例如,在 HTTP 中,它從 HttpServletRequest
讀取,並將響應寫回 HttpServletResponse
。
SOAP 協議的一個關鍵特性是它試圖實現傳輸無關。這就是為什麼 Spring-WS 不透過 HTTP 請求 URL 對映訊息到端點,而是透過訊息內容進行對映的原因。
然而,有時需要訪問底層傳輸,無論是在客戶端還是伺服器端。為此,Spring Web Services 提供了 TransportContext
。傳輸上下文允許訪問底層的 WebServiceConnection
,這通常在伺服器端是 HttpServletConnection
;或在客戶端是 HttpUrlConnection
或 CommonsHttpConnection
。例如,你可以在伺服器端端點或攔截器中像這樣獲取當前請求的 IP 地址:
TransportContext context = TransportContextHolder.getTransportContext(); HttpServletConnection connection = (HttpServletConnection )context.getConnection(); HttpServletRequest request = connection.getHttpServletRequest(); String ipAddress = request.getRemoteAddr();
處理 XML 的最佳方法之一是使用 XPath。引用 [effective-xml],第 35 條:
XPath 是一種第四代宣告性語言,它允許您指定要處理哪些節點,而無需指定處理器應如何準確導航到這些節點。XPath 的資料模型設計得非常好,能夠支援幾乎所有開發人員對 XML 所期望的內容。例如,它合併所有相鄰文字(包括 CDATA 段中的文字),允許計算跳過註釋和處理指令的值,幷包括來自子元素和後代元素的文字,並要求解析所有外部實體引用。實際上,XPath 表示式往往對輸入文件中意外但可能不重要的更改更加健壯。 | ||
--Elliotte Rusty Harold |
Spring Web Services 有兩種在應用程式中使用 XPath 的方式:更快的 XPathExpression
或更靈活的 XPathTemplate
。
XPathExpression
是對編譯後的 XPath 表示式的抽象,例如 Java 5 的 javax.xml.xpath.XPathExpression
或 Jaxen 的 XPath
類。要在應用程式上下文中構建表示式,可以使用 XPathExpressionFactoryBean
。以下是使用此工廠 bean 的示例:
<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 屬性來設定它們。該表示式可以在程式碼中按如下方式使用:
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
。以下示例展示了我們如何使用它:
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());
}
});
// do something with list of Contact
objects
}
}
與 Spring JDBC 的 RowMapper
中對映行類似,每個結果節點都使用一個匿名內部類進行對映。在本例中,我們建立了一個 Contact
物件,我們稍後會使用它。
XPathExpression
只允許你評估單個預編譯表示式。更靈活(但較慢)的替代方案是 XpathTemplate
。此類遵循 Spring 中常見的模板模式(JdbcTemplate, JmsTemplate 等)。以下是一個示例:
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
}
}
在開發或除錯 Web service 時,檢視 (SOAP) 訊息到達時或傳送前的內容非常有用。Spring Web Services 透過標準的 Commons Logging 介面提供了此功能。
請確保使用 Commons Logging 1.1 或更高版本。早期版本存在類載入問題,並且不與 Log4J TRACE 級別整合。
要記錄所有伺服器端訊息,只需將 org.springframework.ws.server.MessageTracing
logger 設定為 DEBUG 或 TRACE 級別。在 DEBUG 級別,只記錄負載根元素;在 TRACE 級別,記錄整個訊息內容。如果你只想記錄已傳送訊息,請使用 org.springframework.ws.server.MessageTracing.sent
logger;或使用 org.springframework.ws.server.MessageTracing.received
來記錄已接收訊息。
在客戶端,存在類似的 logger:org.springframework.ws.client.MessageTracing.sent
和 org.springframework.ws.client.MessageTracing.received
。
以下是一個 log4j.properties
配置示例,記錄客戶端傳送訊息的完整內容,以及客戶端接收訊息的負載根元素。在伺服器端,記錄已傳送和已接收訊息的負載根元素。
log4j.rootCategory=INFO, stdout log4j.logger.org.springframework.ws.client.MessageTracing.sent=TRACE log4j.logger.org.springframework.ws.client.MessageTracing.received=DEBUG log4j.logger.org.springframework.ws.server.MessageTracing=DEBUG log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%p [%c{3}] %m%n
透過此配置,典型的輸出將是:
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] ...