XMPP 支持

Spring Integration 提供了用于 XMPP 的通道适配器。XMPP 代表“可扩展消息处理和出席协议”。

XMPP 描述了一种在分布式系统中多个代理相互通信的方式。典型的用例是发送和接收聊天消息,尽管 XMPP 可以(并且确实)用于其他类型的应用程序。XMPP 描述了一个参与者网络。在这个网络中,参与者可以直接互相寻址并广播状态更改(例如“出席”)。

XMPP 提供了构成世界上一些最大即时消息网络的基础消息结构,包括 Google Talk(GTalk,也可从 GMail 中访问)和 Facebook 聊天。许多优秀的开源 XMPP 服务器可用。两种流行的实现是 Openfireejabberd

Spring Integration 通过提供 XMPP 适配器来支持 XMPP,这些适配器支持发送和接收 XMPP 聊天消息以及客户端花名册中其他条目的状态更改。

您需要将此依赖项包含到您的项目中

  • Maven

  • Gradle

<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-xmpp</artifactId>
    <version>6.3.5</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:6.3.5"

与其他适配器一样,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 版本引入了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 还支持广播状态。您可以使用此功能让将您添加到其花名册中的人员看到您的状态更改。这在您的 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/),它允许更复杂地配置 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中,并从中复制出来。

除非DefaultXmppHeaderMapperrequestHeaderNamesreplyHeaderNames属性显式指定,否则不会将任何用户定义的头复制到XMPP消息中或从中复制出来。

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

从4.1版本开始,AbstractHeaderMapperDefaultXmppHeaderMapper的超类)允许您为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之类的列表不会映射thing1thing2thing3。该列表映射标准头加上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资源在类路径中使用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);

有关更多信息,请参见本章前面介绍的入站消息通道适配器出站消息通道适配器