XMPP 支援

Spring Integration 為 XMPP 提供通道配接器。XMPP 代表「可延伸訊息處理和出席協定」(Extensible Messaging and Presence Protocol)。

XMPP 描述多個代理程式在分散式系統中彼此通訊的方式。典型的使用案例是傳送和接收聊天訊息,儘管 XMPP 可以(且確實)用於其他類型的應用程式。XMPP 描述一個參與者網路。在該網路中,參與者可以直接尋址彼此並廣播狀態變更(例如「出席」)。

XMPP 提供的訊息傳遞結構是世界上一些最大的即時訊息網路的基礎,包括 Google Talk(GTalk,也可以從 GMail 內部使用)和 Facebook Chat。許多優良的開放原始碼 XMPP 伺服器可供使用。兩個流行的實作是 Openfireejabberd

Spring Integration 透過提供 XMPP 配接器來支援 XMPP,這些配接器支援從用戶端名單中的其他條目傳送和接收 XMPP 聊天訊息和出席狀態變更。

您需要將此相依性包含到您的專案中

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-xmpp</artifactId>
    <version>6.3.5</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:6.3.5"

與其他配接器一樣,XMPP 配接器也支援基於命名空間的便捷設定。若要設定 XMPP 命名空間,請在 XML 設定檔的標頭中包含以下元素

xmlns:int-xmpp="http://www.springframework.org/schema/integration/xmpp"
xsi:schemaLocation="http://www.springframework.org/schema/integration/xmpp
	https://www.springframework.org/schema/integration/xmpp/spring-integration-xmpp.xsd"

XMPP 連線

在使用輸入或輸出 XMPP 配接器參與 XMPP 網路之前,參與者必須建立其 XMPP 連線。連接到特定帳戶的所有 XMPP 配接器都可以共用此連線物件。通常,這至少需要 userpasswordhost。若要建立基本的 XMPP 連線,您可以使用命名空間的便利性,如下列範例所示

<int-xmpp:xmpp-connection
    id="myConnection"
    user="user"
    password="password"
    host="host"
    port="port"
    resource="theNameOfTheResource"
    subscription-mode="accept_all"/>
為了增加便利性,您可以依賴預設命名慣例並省略 id 屬性。預設名稱 (xmppConnection) 用於此連線 Bean。

如果 XMPP 連線過時,只要先前的連線狀態已記錄(已驗證),就會嘗試重新連線並自動登入。我們也註冊了 ConnectionListener,如果啟用 DEBUG 記錄層級,它會記錄連線事件。

subscription-mode 屬性會啟動名單監聽器以處理來自其他使用者的傳入訂閱。此功能並非始終適用於目標 XMPP 伺服器。例如,Google Cloud Messaging (GCM) 和 Firebase Cloud Messaging (FCM) 完全停用了它。若要關閉訂閱的名單監聽器,您可以在使用 XML 設定時使用空字串 (subscription-mode="") 或在使用 Java 設定時使用 XmppConnectionFactoryBean.setSubscriptionMode(null) 進行設定。這樣做也會在登入階段停用名單。請參閱 Roster.setRosterLoadedAtLogin(boolean) 以取得更多資訊。

XMPP 訊息

Spring Integration 提供傳送和接收 XMPP 訊息的支援。為了接收它們,它提供了一個輸入訊息通道配接器。為了傳送它們,它提供了一個輸出訊息通道配接器。

輸入訊息通道配接器

Spring Integration 配接器支援從系統中的其他使用者接收聊天訊息。為此,輸入訊息通道配接器會以使用者的身分「登入」,並接收傳送給該使用者的訊息。然後,這些訊息會轉發到您的 Spring Integration 用戶端。inbound-channel-adapter 元素提供對 XMPP 輸入訊息通道配接器的設定支援。以下範例顯示如何設定它

<int-xmpp:inbound-channel-adapter id="xmppInboundAdapter"
	channel="xmppInbound"
	xmpp-connection="testConnection"
	payload-expression="getExtension('google:mobile:data').json"
	stanza-filter="stanzaFilter"
	auto-startup="true"/>

除了通常的屬性(對於訊息通道配接器)之外,此配接器還需要參考 XMPP 連線。

XMPP 輸入配接器是事件驅動的,並且是 Lifecycle 實作。啟動後,它會註冊一個 PacketListener,以監聽傳入的 XMPP 聊天訊息。它會將收到的任何訊息轉發到基礎配接器,後者會將它們轉換為 Spring Integration 訊息並將它們傳送到指定的 channel。停止時,它會取消註冊 PacketListener

從 4.3 版開始,ChatMessageListeningEndpoint(及其 <int-xmpp:inbound-channel-adapter>)支援注入 org.jivesoftware.smack.filter.StanzaFilter,以便與內部 StanzaListener 實作一起在提供的 XMPPConnection 上註冊。請參閱 Javadoc 以取得更多資訊。

4.3 版為 ChatMessageListeningEndpoint 引入了 payload-expression 屬性。傳入的 org.jivesoftware.smack.packet.Message 代表評估內容的根物件。當您使用 XMPP 擴充功能時,此選項很有用。例如,對於 GCM 協定,我們可以透過使用以下運算式來擷取主體

payload-expression="getExtension('google:mobile:data').json"

以下範例擷取 XHTML 協定的主體

payload-expression="getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]"

為了簡化對 XMPP 訊息中擴充功能的存取,extension 變數會新增到 EvaluationContext 中。請注意,僅當訊息中存在一個擴充功能時才會新增它。先前顯示 namespace 操作的範例可以簡化為以下範例

payload-expression="#extension.json"
payload-expression="#extension.bodies[0]"

輸出訊息通道配接器

您也可以使用輸出訊息通道配接器將聊天訊息傳送給 XMPP 上的其他使用者。outbound-channel-adapter 元素提供對 XMPP 輸出訊息通道配接器的設定支援。

<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
						channel="outboundEventChannel"
						xmpp-connection="testConnection"/>

配接器預期其輸入至少是 java.lang.String 類型的 Payload,以及 XmppHeaders.CHAT_TO 的標頭值,後者指定訊息應傳送給哪個使用者。若要建立訊息,您可以使用類似於以下的 Java 程式碼

Message<String> xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" )
						.setHeader(XmppHeaders.CHAT_TO, "userhandle")
						.build();

您也可以使用 XMPP 標頭豐富器支援來設定標頭,如下列範例所示

<int-xmpp:header-enricher input-channel="input" output-channel="output">
	<int-xmpp:chat-to value="[email protected]"/>
</int-xmpp:header-enricher>

從 4.3 版開始,封包擴充功能支援已新增到 ChatMessageSendingMessageHandler(XML 設定中的 <int-xmpp:outbound-channel-adapter>)。除了常規的 Stringorg.jivesoftware.smack.packet.Message Payload 之外,現在您可以傳送 Payload 為 org.jivesoftware.smack.packet.XmlElement 的訊息(它會填入 org.jivesoftware.smack.packet.Message.addExtension())而不是 setBody()。為了方便起見,我們為 ChatMessageSendingMessageHandler 新增了一個 extension-provider 選項。它允許您注入 org.jivesoftware.smack.provider.ExtensionElementProvider,後者會針對執行階段的 Payload 建置 XmlElement。對於這種情況,Payload 必須是 JSON 或 XML 格式的字串,具體取決於 XEP 協定。

XMPP 出席狀態

XMPP 也支援廣播狀態。您可以使用此功能讓名單上有您的人看到您的狀態變更。這在您的 IM 用戶端上一直發生。您變更您的離開狀態並設定離開訊息,並且名單上有您的每個人都會看到您的圖示或使用者名稱變更以反映此新狀態,並且可能會看到您的新「離開」訊息。如果您想要接收通知或通知其他人狀態變更,您可以使用 Spring Integration 的「出席狀態」配接器。

輸入出席狀態訊息通道配接器

Spring Integration 提供了一個輸入出席狀態訊息通道配接器,它支援從系統中名單上的其他使用者接收出席狀態事件。為此,配接器會以使用者的身分「登入」,註冊一個 RosterListener,並將收到的出席狀態更新事件作為訊息轉發到由 channel 屬性識別的通道。訊息的 Payload 是一個 org.jivesoftware.smack.packet.Presence 物件(請參閱 www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html)。

presence-inbound-channel-adapter 元素提供對 XMPP 輸入出席狀態訊息通道配接器的設定支援。以下範例設定輸入出席狀態訊息通道配接器

<int-xmpp:presence-inbound-channel-adapter channel="outChannel"
		xmpp-connection="testConnection" auto-startup="false"/>

除了通常的屬性之外,此配接器還需要參考 XMPP 連線。此配接器是事件驅動的,並且是 Lifecycle 實作。它在啟動時註冊 RosterListener,並在停止時取消註冊該 RosterListener

輸出出席狀態訊息通道配接器

Spring Integration 也支援傳送出席狀態事件,以供網路上碰巧有名單上有您的其他使用者看到。當您將訊息傳送到輸出出席狀態訊息通道配接器時,它會擷取 Payload(預期為 org.jivesoftware.smack.packet.Presence 類型)並將其傳送到 XMPP 連線,從而向網路的其餘部分宣告您的出席狀態事件。

presence-outbound-channel-adapter 元素提供對 XMPP 輸出出席狀態訊息通道配接器的設定支援。以下範例顯示如何設定輸出出席狀態訊息通道配接器

<int-xmpp:presence-outbound-channel-adapter id="eventOutboundPresenceChannel"
	xmpp-connection="testConnection"/>

它也可以是輪詢消費者(如果它從可輪詢通道接收訊息),在這種情況下,您需要註冊一個 poller。以下範例顯示如何執行此操作

<int-xmpp:presence-outbound-channel-adapter id="pollingOutboundPresenceAdapter"
		xmpp-connection="testConnection"
		channel="pollingChannel">
	<int:poller fixed-rate="1000" max-messages-per-poll="1"/>
</int-xmpp:presence-outbound-channel-adapter>

與其輸入對應項一樣,它需要參考 XMPP 連線。

如果您依賴 XMPP 連線 Bean 的預設命名慣例(如前所述),並且您的應用程式內容中僅設定了一個 XMPP 連線 Bean,則可以省略 xmpp-connection 屬性。在這種情況下,將會找到名為 xmppConnection 的 Bean 並將其注入到配接器中。

進階設定

Spring Integration 的 XMPP 支援基於 Smack 4.0 API(www.igniterealtime.org/projects/smack/),它允許更複雜的 XMPP 連線物件設定。

先前所述xmpp-connection 命名空間支援旨在簡化基本連線設定,並且僅支援少數常見的設定屬性。但是,org.jivesoftware.smack.ConnectionConfiguration 物件定義了大約 20 個屬性,並且為所有這些屬性新增命名空間支援沒有實際價值。因此,對於更複雜的連線設定,您可以將我們的 XmppConnectionFactoryBean 的實例設定為常規 Bean,並將 org.jivesoftware.smack.ConnectionConfiguration 作為建構子引數注入到該 FactoryBean。您可以直接在該 ConnectionConfiguration 實例上指定您需要的每個屬性。(具有 'p' 命名空間的 Bean 定義將會很好地工作。)這樣,您可以直接設定 SSL(或任何其他屬性)。以下範例顯示如何執行此操作

<bean id="xmppConnection" class="o.s.i.xmpp.XmppConnectionFactoryBean">
    <constructor-arg>
        <bean class="org.jivesoftware.smack.ConnectionConfiguration">
            <constructor-arg value="myServiceName"/>
            <property name="socketFactory" ref="..."/>
        </bean>
    </constructor-arg>
</bean>

<int:channel id="outboundEventChannel"/>

<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
    channel="outboundEventChannel"
    xmpp-connection="xmppConnection"/>

Smack API 也提供靜態初始化器,這可能很有幫助。對於更複雜的情況(例如註冊 SASL 機制),您可能需要執行某些靜態初始化器。其中一個靜態初始化器是 SASLAuthentication,它允許您註冊支援的 SASL 機制。對於該複雜程度,我們建議使用 Spring Java 設定來進行 XMPP 連線設定。這樣,您可以透過 Java 程式碼設定整個元件,並在適當的時間執行所有其他必要的 Java 程式碼,包括靜態初始化器。以下範例顯示如何在 Java 中使用 SASL(簡單驗證和安全層)設定 XMPP 連線

@Configuration
public class CustomConnectionConfiguration {
  @Bean
  public XMPPConnection xmppConnection() {
	SASLAuthentication.supportSASLMechanism("EXTERNAL", 0); // static initializer

	ConnectionConfiguration config = new ConnectionConfiguration("localhost", 5223);
	config.setKeystorePath("path_to_truststore.jks");
	config.setSecurityEnabled(true);
	config.setSocketFactory(SSLSocketFactory.getDefault());
	return new XMPPConnection(config);
  }
}

有關使用 Java 進行應用程式內容設定的更多資訊,請參閱 Spring 參考手冊中的以下章節。

XMPP 訊息標頭

Spring Integration XMPP 配接器會自動對應標準 XMPP 屬性。預設情況下,這些屬性會使用 DefaultXmppHeaderMapper 複製到 Spring Integration MessageHeaders 和從 Spring Integration MessageHeaders 複製。

除非由 DefaultXmppHeaderMapperrequestHeaderNamesreplyHeaderNames 屬性明確指定,否則任何使用者定義的標頭都不會複製到 XMPP 訊息或從 XMPP 訊息複製。

在對應使用者定義的標頭時,值也可以包含簡單的萬用字元模式(例如 "thing*" 或 "*thing")。

從 4.1 版開始,AbstractHeaderMapperDefaultXmppHeaderMapper 的父類別)允許您為 requestHeaderNames 屬性設定 NON_STANDARD_HEADERS 權杖(除了 STANDARD_REQUEST_HEADERS 之外),以對應所有使用者定義的標頭。

org.springframework.xmpp.XmppHeaders 類別識別 DefaultXmppHeaderMapper 要使用的預設標頭

  • xmpp_from

  • xmpp_subject

  • xmpp_thread

  • xmpp_to

  • xmpp_type

從 4.3 版開始,您可以透過在模式前面加上 ! 來否定標頭對應中的模式。否定的模式具有優先順序,因此諸如 STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1 之類的清單不會對應 thing1thing2thing3。該清單會對應標準標頭加上 thing4qux

如果您有以 ! 開頭的使用者定義標頭,但您確實希望對應它,則可以使用 \ 來逸出它,如下所示:STANDARD_REQUEST_HEADERS,\!myBangHeader。在該範例中,標準請求標頭和 !myBangHeader 會被對應。

XMPP 擴充功能

擴充功能將「可延伸」放入「可延伸訊息處理和出席協定」中。

XMPP 基於 XML,這是一種支援稱為命名空間概念的資料格式。透過命名空間,您可以將未在原始規格中定義的位元新增到 XMPP。XMPP 規格刻意僅描述一組核心功能

  • 用戶端如何連線到伺服器

  • 加密 (SSL/TLS)

  • 驗證

  • 伺服器如何彼此通訊以轉發訊息

  • 一些其他基本建構區塊

一旦您實作了這一點,您就擁有了一個 XMPP 用戶端,並且可以傳送您喜歡的任何種類的資料。但是,您可能需要做的不僅僅是基本操作。例如,您可能需要在訊息中包含格式設定(粗體、斜體等等),這在核心 XMPP 規格中未定義。嗯,您可以想出一种方法來做到這一點,但是,除非其他每個人都以與您相同的方式執行此操作,否則沒有其他軟體可以解釋它(它們會忽略它們無法理解的命名空間)。

為了解決這個問題,XMPP 標準基金會 (XSF) 發佈了一系列額外文件,稱為 XMPP 擴充協定 (XEP)。一般來說,每個 XEP 都描述一個特定的活動(從訊息格式設定到檔案傳輸、多人聊天等等)。它們還為每個人提供了一個用於該活動的標準格式。

Smack API 透過其 extensionsexperimental 專案提供了許多 XEP 實作。從 Spring Integration 4.3 版開始,您可以將任何 XEP 與現有的 XMPP 通道配接器一起使用。

為了能夠處理 XEP 或任何其他自訂 XMPP 擴充功能,您必須提供 Smack 的 ProviderManager 預先設定。您可以使用 static Java 程式碼來執行此操作,如下列範例所示

ProviderManager.addIQProvider("element", "namespace", new MyIQProvider());
ProviderManager.addExtensionProvider("element", "namespace", new MyExtProvider());

您也可以在特定實例中使用 .providers 設定檔,並使用 JVM 引數存取它,如下列範例所示

-Dsmack.provider.file=file:///c:/my/provider/mycustom.providers

mycustom.providers 檔案可能如下所示

<?xml version="1.0"?>
<smackProviders>
<iqProvider>
    <elementName>query</elementName>
    <namespace>jabber:iq:time</namespace>
    <className>org.jivesoftware.smack.packet.Time</className>
</iqProvider>

<iqProvider>
    <elementName>query</elementName>
    <namespace>https://jabber.org/protocol/disco#items</namespace>
    <className>org.jivesoftware.smackx.provider.DiscoverItemsProvider</className>
</iqProvider>

<extensionProvider>
    <elementName>subscription</elementName>
    <namespace>https://jabber.org/protocol/pubsub</namespace>
    <className>org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider</className>
</extensionProvider>
</smackProviders>

例如,最流行的 XMPP 訊息傳遞擴充功能是 Google Cloud Messaging (GCM)。Smack 程式庫為此目的提供了 org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider。預設情況下,它會透過使用 experimental.providers 資源在類別路徑中的 smack-experimental jar 中註冊該類別,如下列 Maven 範例所示

<!-- GCM JSON payload -->
<extensionProvider>
    <elementName>gcm</elementName>
    <namespace>google:mobile:data</namespace>
    <className>org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider</className>
</extensionProvider>

此外,GcmPacketExtension 讓目標訊息傳遞協定剖析傳入的封包並建置傳出的封包,如下列範例所示

GcmPacketExtension gcmExtension = (GcmPacketExtension) xmppMessage.getExtension(GcmPacketExtension.NAMESPACE);
String message = gcmExtension.getJson());
GcmPacketExtension packetExtension = new GcmPacketExtension(gcmJson);
Message smackMessage = new Message();
smackMessage.addExtension(packetExtension);

請參閱本章稍早的Inbound Message Channel AdapterOutbound Message Channel Adapter 以取得更多資訊。