XMPP 支援

Spring Integration 為 XMPP 提供了通道介面卡。XMPP 代表“可擴充套件訊息和狀態協議”(Extensible Messaging and Presence Protocol)。

XMPP 描述了一種在分散式系統中多個代理互相通訊的方式。其典型用例是傳送和接收聊天訊息,儘管 XMPP 可以(並且已經)用於其他型別的應用。XMPP 描述了一個由參與者組成的網路。在該網路中,參與者可以直接互相定址並廣播狀態變化(例如“presence”,線上狀態)。

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.4.4</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:6.4.4"

與其他介面卡一樣,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 版本引入了 ChatMessageListeningEndpointpayload-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 的載荷以及一個用於 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 載荷外,現在你可以傳送一個載荷為 org.jivesoftware.smack.packet.XmlElement 的訊息(該載荷會填充到 org.jivesoftware.smack.packet.Message.addExtension()),而不是使用 setBody()。為了方便,我們為 ChatMessageSendingMessageHandler 添加了一個 extension-provider 選項。它允許你注入 org.jivesoftware.smack.provider.ExtensionElementProvider,該提供者在執行時根據載荷構建 XmlElement。在這種情況下,載荷必須是 JSON 或 XML 格式的字串,具體取決於 XEP 協議。

XMPP 線上狀態

XMPP 也支援廣播狀態。你可以利用此功能讓花名冊上的人看到你的狀態變化。這在你的即時通訊客戶端中一直髮生。你改變你的離開狀態並設定離開訊息,花名冊上的每個人都會看到你的圖示或使用者名稱隨之變化,並可能看到你的新“離開”訊息。如果你想接收通知或通知他人狀態變化,可以使用 Spring Integration 的“線上狀態”介面卡。

入站線上狀態訊息通道介面卡

Spring Integration 提供入站線上狀態訊息通道介面卡,支援接收來自系統中你的花名冊上其他使用者的線上狀態事件。為此,介面卡將代表你“登入”為一個使用者,註冊一個 RosterListener,並將接收到的線上狀態更新事件作為訊息轉發到由 channel 屬性標識的通道。訊息的載荷是一個 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 還支援傳送線上狀態事件,供網路中恰好將你新增到花名冊的其他使用者檢視。當你向出站線上狀態訊息通道介面卡傳送訊息時,它會提取載荷(該載荷應為 org.jivesoftware.smack.packet.Presence 型別)並將其傳送到 XMPP 連線,從而向網路的其他部分廣播你的線上狀態事件。

presence-outbound-channel-adapter 元素為 XMPP 出站線上狀態訊息通道介面卡提供配置支援。下例展示瞭如何配置出站線上狀態訊息通道介面卡

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

它也可以是輪詢消費者(如果它從一個可輪詢的通道接收訊息),在這種情況下你需要註冊一個輪詢器。下例展示瞭如何進行配置

<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/),該 API 允許更復雜的 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 中或從其中複製出來。

任何使用者定義的頭都不會複製到 XMPP 訊息中或從其中複製出來,除非在 DefaultXmppHeaderMapperrequestHeaderNamesreplyHeaderNames 屬性中明確指定。

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

從 4.1 版本開始,AbstractHeaderMapperDefaultXmppHeaderMapper 的超類)允許你為 requestHeaderNames 屬性配置 NON_STANDARD_HEADERS token(除了 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 這樣的列表將不會對映 thing1, thing2thing3。該列表將對映標準頭以及 thing4qux

如果你有一個使用者定義的頭以 ! 開頭並且你確實想對映它,可以使用 \ 進行轉義,例如:STANDARD_REQUEST_HEADERS,\!myBangHeader。在該示例中,標準請求頭和 !myBangHeader 將被對映。

XMPP 擴充套件

擴充套件使“可擴充套件訊息和狀態協議”中的“可擴充套件”得以體現。

XMPP 基於 XML,這是一種支援名稱空間概念的資料格式。透過名稱空間,你可以向 XMPP 新增原始規範中未定義的部分。XMPP 規範特意僅描述了一組核心功能

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

  • 加密 (SSL/TLS)

  • 認證

  • 伺服器如何互相通訊以中繼訊息

  • 其他一些基本構建塊

一旦你實現了這些,你就擁有了一個 XMPP 客戶端,可以傳送任何你喜歡的資料。然而,你可能需要做比基礎功能更多的事情。例如,你可能需要在訊息中包含格式(粗體、斜體等),這在核心 XMPP 規範中並未定義。好吧,你可以自己想一種方法來實現,但除非其他人也這樣做,否則其他軟體無法解釋它(它們會忽略無法理解的名稱空間)。

為了解決這個問題,XMPP 標準基金會 (XSF) 釋出了一系列額外文件,稱為 XMPP 擴充套件協議 (XEPs)。一般來說,每個 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 資源在 classpath 中註冊位於 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);

更多資訊請參閱本章前面的 入站訊息通道介面卡出站訊息通道介面卡