XMPP 支持
Spring Integration 为 XMPP 提供了通道适配器。
XMPP 代表“可扩展消息和存在协议”(“Extensible Messaging and Presence Protocol”)。
XMPP 描述了一种在分布式系统中多个代理之间进行通信的方式。
典型的用例是发送和接收聊天消息,尽管 XMPP 可以(也确实)用于其他类型的应用程序。
XMPP 描述了一个参与者网络。
在该网络中,参与者可以直接相互寻址并广播状态变化(例如“存在”)。
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>{project-version}</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:{project-version}"
与其他适配器一样,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"/>
|
为了方便起见,你可以依赖默认命名约定并省略 |
如果 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-extensions 时,此选项很有用。
例如,对于 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="test1@example.org"/>
</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 对象(请参阅 [role="bare"][role="bare"]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-connection),并且你的应用程序上下文中只配置了一个 XMPP 连接 Bean,则可以省略 |
高级配置
Spring Integration 的 XMPP 支持基于 Smack 4.0 API ([role="bare"][role="bare"]www.igniterealtime.org/projects/smack/),它允许更复杂的 XMPP 连接对象配置。
如 xmpp-connection,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 消息或从 XMPP 消息复制,除非 DefaultXmppHeaderMapper 的 requestHeaderNames 或 replyHeaderNames 属性明确指定。
|
映射用户定义的头部时,值还可以包含简单的通配符模式(例如“thing*”或“*thing”)。 |
从 4.1 版本开始,AbstractHeaderMapper(DefaultXmppHeaderMapper 的超类)允许你为 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 这样的列表不会映射 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 资源(如下面的 Maven 示例所示)在类路径中注册带有 smack-experimental jar 的该类:
<!-- 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);
有关更多信息,请参阅本章前面介绍的 xmpp-message-inbound-channel-adapter 和 xmpp-message-outbound-channel-adapter。