XMPP 支援
Spring Integration 為 XMPP 提供了通道介面卡。XMPP 代表“可擴充套件訊息和狀態協議”(Extensible Messaging and Presence Protocol)。
XMPP 描述了一種在分散式系統中多個代理互相通訊的方式。其典型用例是傳送和接收聊天訊息,儘管 XMPP 可以(並且已經)用於其他型別的應用。XMPP 描述了一個由參與者組成的網路。在該網路中,參與者可以直接互相定址並廣播狀態變化(例如“presence”,線上狀態)。
XMPP 提供了支撐全球一些最大即時訊息網路的訊息基礎結構,包括 Google Talk (GTalk,也可在 GMail 中使用) 和 Facebook Chat。有許多優秀的開源 XMPP 伺服器可用。兩個流行的實現是 Openfire 和 ejabberd。
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 介面卡可以共享此連線物件。通常,這至少需要 user
、password
和 host
。要建立基本的 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
的載荷以及一個用於 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>
)。除了常規的 String
和 org.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 訊息中或從其中複製出來,除非在 DefaultXmppHeaderMapper
的 requestHeaderNames
或 replyHeaderNames
屬性中明確指定。
對映使用者定義的頭時,值也可以包含簡單的萬用字元模式(例如 "thing*" 或 "*thing")。 |
從 4.1 版本開始,AbstractHeaderMapper
(DefaultXmppHeaderMapper
的超類)允許你為 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
, thing2
或 thing3
。該列表將對映標準頭以及 thing4
和 qux
。
如果你有一個使用者定義的頭以 ! 開頭並且你確實想對映它,可以使用 \ 進行轉義,例如:STANDARD_REQUEST_HEADERS,\!myBangHeader 。在該示例中,標準請求頭和 !myBangHeader 將被對映。 |
XMPP 擴充套件
擴充套件使“可擴充套件訊息和狀態協議”中的“可擴充套件”得以體現。
XMPP 基於 XML,這是一種支援名稱空間概念的資料格式。透過名稱空間,你可以向 XMPP 新增原始規範中未定義的部分。XMPP 規範特意僅描述了一組核心功能
-
客戶端如何連線到伺服器
-
加密 (SSL/TLS)
-
認證
-
伺服器如何互相通訊以中繼訊息
-
其他一些基本構建塊
一旦你實現了這些,你就擁有了一個 XMPP 客戶端,可以傳送任何你喜歡的資料。然而,你可能需要做比基礎功能更多的事情。例如,你可能需要在訊息中包含格式(粗體、斜體等),這在核心 XMPP 規範中並未定義。好吧,你可以自己想一種方法來實現,但除非其他人也這樣做,否則其他軟體無法解釋它(它們會忽略無法理解的名稱空間)。
為了解決這個問題,XMPP 標準基金會 (XSF) 釋出了一系列額外文件,稱為 XMPP 擴充套件協議 (XEPs)。一般來說,每個 XEP 描述一個特定的活動(從訊息格式化到檔案傳輸、多使用者聊天等等)。它們還為每個人使用該活動提供了一個標準格式。
Smack API 透過其 extensions
和 experimental
專案 提供了許多 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);