XMPP 支持

Spring Integration 为 XMPP 提供通道适配器。XMPP 代表“可扩展消息和状态协议”。

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

与其他适配器一样,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属性。此连接 bean 使用默认名称(xmppConnection)。

如果 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 以在提供的 XMPPConnection 上注册,以及一个内部 StanzaListener 实现。有关更多信息,请参阅 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>)。除了常规的 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 还支持广播状态。您可以使用此功能让将您添加到联系人列表的人员看到您的状态更改。这种情况在您的 IM 客户端中一直发生。您更改您的离开状态并设置离开消息,并且将您添加到其联系人列表中的每个人都会看到您的图标或用户名更改以反映此新状态,并且可能会看到您的新“离开”消息。如果您想接收通知或通知他人状态更改,可以使用 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 机制。对于这种复杂程度,我们建议对 XMPP 连接配置使用 Spring Java 配置。这样,您可以通过 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

除非通过 DefaultXmppHeaderMapperrequestHeaderNamesreplyHeaderNames 属性明确指定,否则不会将任何用户定义的头复制到或从 XMPP 消息。

在映射用户定义的头时,值还可以包含简单的通配符模式(例如“thing*”或“*thing”)。

从 4.1 版开始,AbstractHeaderMapperDefaultXmppHeaderMapper 的超类)允许您为 requestHeaderNames 属性(除了 STANDARD_REQUEST_HEADERS 外)配置 NON_STANDARD_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 projects 提供许多 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);

有关更多信息,请参阅本章前面部分的 入站消息通道适配器出站消息通道适配器