XMPP Support

Spring Integration 为 XMPP 提供通道适配器。XMPP 代表 “Extensible Messaging and Presence Protocol”。 XMPP 描述了一种在分布式系统中多个代理互相通信的方式。规范的用例是发送和接收聊天消息,尽管 XMPP 可以(并且已)用于其他类型的应用程序。XMPP 描述了一个演员网络。在该网络中,演员可以直接互相联系并广播状态更改(例如,“状态”)。 XMPP 提供消息传递结构,它是世界上一些最大的即时消息网络的基础,包括 Google Talk(GTalk,也可以从 GMail 中使用)和 Facebook 聊天。有许多优秀的开源 XMPP 服务器可用。两个流行的实现是 Openfireejabberd。 Spring Integration 通过提供 XMPP 适配器来为 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 Connection

在使用入站或出站 XMPP 适配器参与 XMPP 网络之前,一个演员必须建立其 XMPP 连接。连接到特定帐户的所有 XMPP 适配器都可以共享此连接对象。通常,这至少需要“用户”、“密码”和“主机”。要创建基本的 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 Messages

Spring Integration 为发送和接收 XMPP 消息提供支持。为接收它们,它提供了一个入站消息通道适配器。为发送它们,它提供了一个出站消息通道适配器。

Inbound Message Channel Adapter

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 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]"

Outbound Message Channel Adapter

您还可以使用出站消息通道适配器向 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>)。除了常规的 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 Presence

XMPP 还支持广播状态。您可以使用此功能让您通讯录中的人查看您的状态更改。这始终发生在您的 IM 客户端中。您更改为离开状态并设置离开消息,通讯录中拥有您的人会看到您的图标或用户名发生更改以反映此新状态,并且可能会看到您的新“离开”消息。如果您想接收通知或通知其他人状态已更改,可以使用 Spring Integration 的“presence”适配器。

Inbound Presence Message Channel Adapter

Spring Integration 提供了一个入站出席消息通道适配器,它支持从您花名册中的系统中其他人那里接收出席事件。为此,适配器 “logs in” 代表您作为用户注册 RosterListener,并将接收到的出席更新事件转发为消息,发送到由 channel 属性标识的通道。消息的有效负载为 org.jivesoftware.smack.packet.Presence 对象(请参阅 [role="bare"][role="bare"]https://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

Outbound Presence Message Channel Adapter

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(described earlier)的默认命名约定,并且仅在应用程序上下文中配置了一个 XMPP 连接 bean,那么您可以省略 xmpp-connection 属性。在这种情况下,将会找到并注入名为 xmppConnection 的 bean 到此适配器中。

Advanced Configuration

Spring Integration 的 XMPP 支持基于 Smack 4.0 API ([role="bare"][role="bare"]https://www.igniterealtime.org/projects/smack/),该 API 允许对 XMPP 连接对象进行更为复杂的配置。

作为 stated earlierxmpp-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 Reference Manual 中的以下部分。

XMPP Message Headers

Spring Integration XMPP Adapter 会自动映射标准 XMPP 属性。默认情况下,这些属性使用 DefaultXmppHeaderMapper 技术从 Spring Integration MessageHeaders 复制到其中。

除非明确由 DefaultXmppHeaderMapperrequestHeaderNamesreplyHeaderNames 属性指定,否则任何用户定义头都不被复制到 XMPP 消息中或从中复制。

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

从 4.1 版开始,AbstractHeaderMapperDefaultXmppHeaderMapper 的超类)允许您将 NON_STANDARD_HEADERS 令牌配置为 requestHeaderNames 属性(除了 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 Extensions

扩展在“可扩展消息和状态协议”中添加了“可扩展性”。

XMPP 基于 XML,这是一种支持称为命名空间的概念的数据格式。通过命名空间,您可以在 XMPP 中添加未在原始规范中定义的内容。XMPP 规范故意只描述了一组核心功能:

  • 客户端如何连接到服务器

  • Encryption (SSL/TLS)

  • Authentication

  • 服务器如何相互通信中继消息

  • 其他一些基本构件

一旦实现了这一点,您就拥有了一个 XMPP 客户端,并且可以发送任何类型的数据。但是,您可能需要做更多的事情。例如,您可能需要在消息中包含格式(粗体、斜体等),而这未在核心 XMPP 规范中定义。那么,您可以想办法实现这一点,但除非每个人都以您相同的方式进行操作,否则没有其他软件可以解释它(他们会忽略他们无法理解的命名空间)。

为解决该问题,XMPP 标准化基金会 (XSF) 发布了一系列称为 XMPP Extension Protocols (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);

请参阅本章早些时候的 Inbound Message Channel AdapterOutbound Message Channel Adapter 以获取更多信息。