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

XMPP 描述了一种在分布式系统中多个代理相互通信的方法。 规范用例是发送和接收聊天消息,尽管 XMPP 可以(并且正在)用于其他类型的应用程序。 XMPP 描述了参与者网络。 在该网络中,参与者可以直接相互称呼并广播状态更改(例如“状态”)。Spring中文文档

XMPP 提供消息传递结构,这些结构是世界上一些最大的即时消息网络的基础,包括 Google Talk(GTalk,也可在 GMail 内部获得)和 Facebook Chat。 有许多好的开源 XMPP 服务器可用。 两种流行的实现是 OpenfireejabberdSpring中文文档

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

您需要将此依赖项包含在项目中:Spring中文文档

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

与其他适配器一样,XMPP 适配器支持基于命名空间的便捷配置。 若要配置 XMPP 命名空间,请在 XML 配置文件的标头中包含以下元素:Spring中文文档

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 适配器都可以共享此连接对象。 通常,这需要(至少)、 和 。 若要创建基本的 XMPP 连接,可以使用命名空间的便利性,如以下示例所示:userpasswordhostSpring中文文档

<int-xmpp:xmpp-connection
    id="myConnection"
    user="user"
    password="password"
    host="host"
    port="port"
    resource="theNameOfTheResource"
    subscription-mode="accept_all"/>
为方便起见,您可以依赖默认命名约定并省略该属性。 缺省名称 () 用于此连接 Bean。idxmppConnection

如果 XMPP 连接过时,只要记录了以前的连接状态(已通过身份验证),就会使用自动登录进行重新连接尝试。 我们还注册了一个 ,如果启用了日志记录级别,则它会记录连接事件。ConnectionListenerDEBUGSpring中文文档

该属性启动名册侦听器以处理来自其他用户的传入订阅。 此功能并不总是可用于目标 XMPP 服务器。 例如,Google Cloud Messaging (GCM) 和 Firebase Cloud Messaging (FCM) 完全禁用它。 要关闭预订的名册侦听器,可以在使用 XML 配置 () 时或在使用 Java 配置时使用空字符串来配置它。 这样做也会在登录阶段禁用名册。 有关详细信息,请参阅 Roster.setRosterLoadedAtLogin(boolean)。subscription-modesubscription-mode=""XmppConnectionFactoryBean.setSubscriptionMode(null)Spring中文文档

为方便起见,您可以依赖默认命名约定并省略该属性。 缺省名称 () 用于此连接 Bean。idxmppConnection

XMPP 消息

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

入站消息通道适配器

Spring Integration 适配器支持接收来自系统中其他用户的聊天消息。 为此,入站消息通道适配器将代表您以用户身份“登录”,并接收发送给该用户的消息。 然后,这些消息将转发到您的 Spring Integration 客户端。 该元素为 XMPP 入站消息通道适配器提供配置支持。 以下示例演示如何配置它:inbound-channel-adapterSpring中文文档

<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 连接的引用。Spring中文文档

XMPP 入站适配器是事件驱动的,是一种实现。 启动时,它会注册一个侦听传入的 XMPP 聊天消息。 它将任何收到的消息转发到底层适配器,后者将它们转换为 Spring Integration 消息并将它们发送到指定的 . 停止时,它会注销 .LifecyclePacketListenerchannelPacketListenerSpring中文文档

从版本 4.3 开始,(及其 )支持注入要在提供的 上注册的 ,以及内部实现。 有关更多信息,请参见 JavadocChatMessageListeningEndpoint<int-xmpp:inbound-channel-adapter>org.jivesoftware.smack.filter.StanzaFilterXMPPConnectionStanzaListenerSpring中文文档

版本 4.3 引入了 . 传入表示评估上下文的根对象。 当您使用 XMPP 扩展时,此选项非常有用。 例如,对于 GCM 协议,我们可以使用以下表达式提取正文:payload-expressionChatMessageListeningEndpointorg.jivesoftware.smack.packet.MessageSpring中文文档

payload-expression="getExtension('google:mobile:data').json"

下面的示例提取 XHTML 协议的正文:Spring中文文档

payload-expression="getExtension(T(org.jivesoftware.smackx.xhtmlim.packet.XHTMLExtension).NAMESPACE).bodies[0]"

为了简化对 XMPP 消息中扩展的访问,该变量被添加到 . 请注意,当消息中仅存在一个扩展名时,将添加它。 前面演示操作的示例可以简化为以下示例:extensionEvaluationContextnamespaceSpring中文文档

payload-expression="#extension.json"
payload-expression="#extension.bodies[0]"

出站消息通道适配器

您还可以使用出站消息通道适配器向 XMPP 上的其他用户发送聊天消息。 该元素为 XMPP 出站消息通道适配器提供配置支持。outbound-channel-adapterSpring中文文档

<int-xmpp:outbound-channel-adapter id="outboundEventAdapter"
						channel="outboundEventChannel"
						xmpp-connection="testConnection"/>

适配器希望其输入(至少)是类型和标头值的有效负载,该值指定应将消息发送到哪个用户。 若要创建消息,可以使用类似于以下内容的 Java 代码:java.lang.StringXmppHeaders.CHAT_TOSpring中文文档

Message<String> xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" )
						.setHeader(XmppHeaders.CHAT_TO, "userhandle")
						.build();

还可以使用 XMPP 标头扩充器支持来设置标头,如以下示例所示:Spring中文文档

<int-xmpp:header-enricher input-channel="input" output-channel="output">
	<int-xmpp:chat-to value="[email protected]"/>
</int-xmpp:header-enricher>

从版本 4.3 开始,数据包扩展支持已添加到(XML 配置中)。 除了常规负载和有效负载外,现在您可以发送有效负载为 (填充到 ) 而不是 的消息。 为方便起见,我们为 . 它允许您注入 ,这会在运行时针对有效负载构建一个。 在这种情况下,有效负载必须是 JSON 或 XML 格式的字符串,具体取决于 XEP 协议。ChatMessageSendingMessageHandler<int-xmpp:outbound-channel-adapter>Stringorg.jivesoftware.smack.packet.Messageorg.jivesoftware.smack.packet.XmlElementorg.jivesoftware.smack.packet.Message.addExtension()setBody()extension-providerChatMessageSendingMessageHandlerorg.jivesoftware.smack.provider.ExtensionElementProviderXmlElementSpring中文文档

XMPP 状态

XMPP 还支持广播状态。 您可以使用此功能让名册上有您的人员查看您的状态更改。 这种情况一直发生在您的 IM 客户端上。 您更改了离开状态并设置了离开消息,每个将您列入名单的人都会看到您的图标或用户名更改以反映此新状态,并且可能会看到您的新“离开”消息。 如果您想接收通知或通知其他人状态更改,您可以使用 Spring Integration 的“状态”适配器。Spring中文文档

入站状态消息通道适配器

Spring Integration 提供了一个入站状态消息通道适配器,该适配器支持从系统中名册上的其他用户接收状态事件。 为此,适配器以用户身份代表您“登录”,注册 ,并将收到的状态更新事件作为消息转发到由属性标识的通道。 消息的有效负载是一个对象(请参阅 www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html)。RosterListenerchannelorg.jivesoftware.smack.packet.PresenceSpring中文文档

该元素为 XMPP 入站状态消息通道适配器提供配置支持。 以下示例配置入站状态消息通道适配器:presence-inbound-channel-adapterSpring中文文档

<int-xmpp:presence-inbound-channel-adapter channel="outChannel"
		xmpp-connection="testConnection" auto-startup="false"/>

除了通常的属性外,此适配器还需要引用 XMPP 连接。 此适配器是事件驱动的,是一种实现。 它在启动时注册,并在停止时注销。LifecycleRosterListenerRosterListenerSpring中文文档

出站状态消息通道适配器

Spring Integration 还支持发送状态事件,以供网络中碰巧有您的其他用户查看。 当您向出站状态消息通道适配器发送消息时,它会提取有效负载(应为 类型)并将其发送到 XMPP 连接,从而将状态事件通告给网络的其余部分。org.jivesoftware.smack.packet.PresenceSpring中文文档

该元素为 XMPP 出站状态消息通道适配器提供配置支持。 以下示例演示如何配置出站状态消息通道适配器:presence-outbound-channel-adapterSpring中文文档

<int-xmpp:presence-outbound-channel-adapter id="eventOutboundPresenceChannel"
	xmpp-connection="testConnection"/>

它也可以是轮询使用者(如果它从可轮询通道接收消息),在这种情况下,您需要注册轮询器。 以下示例演示如何执行此操作:Spring中文文档

<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 连接。Spring中文文档

如果依赖于 XMPP 连接 Bean 的缺省命名约定(如前所述),并且在应用程序上下文中只配置了一个 XMPP 连接 Bean,则可以省略该属性。 在这种情况下,将找到具有 named 的 Bean 并将其注入适配器中。xmpp-connectionxmppConnection
如果依赖于 XMPP 连接 Bean 的缺省命名约定(如前所述),并且在应用程序上下文中只配置了一个 XMPP 连接 Bean,则可以省略该属性。 在这种情况下,将找到具有 named 的 Bean 并将其注入适配器中。xmpp-connectionxmppConnection

高级配置

Spring Integration 的 XMPP 支持基于 Smack 4.0 API (www.igniterealtime.org/projects/smack/),它允许对 XMPP Connection 对象进行更复杂的配置。Spring中文文档

如前所述,命名空间支持旨在简化基本连接配置,并且仅支持一些常见配置属性。 但是,该对象定义了大约 20 个属性,为所有这些属性添加命名空间支持不会提供任何实际价值。 因此,对于更复杂的连接配置,您可以将 our 的实例配置为常规 bean,并将 a 作为构造函数参数注入到该实例中。 您可以直接在该实例上指定所需的每个属性。 (带有“p”命名空间的 Bean 定义会很好用。 这样,您可以直接设置SSL(或任何其他属性)。 以下示例演示如何执行此操作:xmpp-connectionorg.jivesoftware.smack.ConnectionConfigurationXmppConnectionFactoryBeanorg.jivesoftware.smack.ConnectionConfigurationFactoryBeanConnectionConfigurationSpring中文文档

<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 机制),您可能需要执行某些静态初始值设定项。 其中一个静态初始值设定项是 ,它允许您注册支持的 SASL 机制。 对于这种复杂程度,我们建议使用 Spring Java 配置进行 XMPP 连接配置。 这样,您可以通过 Java 代码配置整个组件,并在适当的时间执行所有其他必要的 Java 代码,包括静态初始值设定项。 以下示例显示了如何在 Java 中使用 SASL(简单身份验证和安全层)配置 XMPP 连接:SASLAuthenticationSpring中文文档

@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 参考手册中的以下部分。Spring中文文档

XMPP 消息头

Spring Integration XMPP 适配器自动映射标准 XMPP 属性。 默认情况下,使用 DefaultXmppHeaderMapper 将这些属性复制到 Spring Integration 或从 Spring Integration 复制。MessageHeadersSpring中文文档

任何用户定义的标头都不会复制到 XMPP 消息或从 XMPP 消息复制,除非 的 或 属性明确指定。requestHeaderNamesreplyHeaderNamesDefaultXmppHeaderMapperSpring中文文档

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

从版本 4.1 开始,(超类 ) 允许您配置属性的令牌(除了 ),以映射所有用户定义的标头。AbstractHeaderMapperDefaultXmppHeaderMapperNON_STANDARD_HEADERSrequestHeaderNamesSTANDARD_REQUEST_HEADERSSpring中文文档

该类标识要使用的默认标头:org.springframework.xmpp.XmppHeadersDefaultXmppHeaderMapperSpring中文文档

从版本 4.3 开始,可以通过在模式前面加上 来否定标头映射中的模式。 否定的模式具有优先级,因此诸如 does not map 、 或 . 该列表确实映射了标准标头加上 和 。!STANDARD_REQUEST_HEADERS,thing1,thing*,!thing2,!thing3,qux,!thing1thing1thing2thing3thing4quxSpring中文文档

如果您有一个用户定义的标头,该标头以您希望映射的开头,则可以使用以下内容对其进行转义: . 在该示例中,映射了标准请求标头 和 。!\STANDARD_REQUEST_HEADERS,\!myBangHeader!myBangHeader
映射用户定义的标头时,这些值还可以包含简单的通配符模式(例如“thing*”或“*thing”)。
如果您有一个用户定义的标头,该标头以您希望映射的开头,则可以使用以下内容对其进行转义: . 在该示例中,映射了标准请求标头 和 。!\STANDARD_REQUEST_HEADERS,\!myBangHeader!myBangHeader

XMPP 扩展

扩展将“可扩展”放在“可扩展消息传递和状态协议”中。Spring中文文档

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

实现此功能后,您就拥有了一个 XMPP 客户端,可以发送您喜欢的任何类型的数据。 但是,您可能需要做的不仅仅是基础知识。 例如,您可能需要在消息中包含格式(粗体、斜体等),而核心 XMPP 规范中未定义这种格式。 好吧,你可以想出一种方法来做到这一点,但是,除非其他人都像你一样做,否则没有其他软件可以解释它(他们忽略了他们无法理解的命名空间)。Spring中文文档

为了解决这个问题,XMPP 标准基金会 (XSF) 发布了一系列额外的文档,称为 XMPP 扩展协议 (XEP)。 通常,每个 XEP 都描述了一个特定的活动(从消息格式化到文件传输、多用户聊天等等)。 它们还为每个人提供了用于该活动的标准格式。Spring中文文档

Smack API 通过其和项目提供了许多 XEP 实现。 从 Spring Integration 版本 4.3 开始,您可以将任何 XEP 与现有的 XMPP 通道适配器一起使用。extensionsexperimentalSpring中文文档

为了能够处理 XEP 或任何其他自定义 XMPP 扩展,您必须提供 Smack 的预配置。 您可以使用 Java 代码执行此操作,如以下示例所示:ProviderManagerstaticSpring中文文档

ProviderManager.addIQProvider("element", "namespace", new MyIQProvider());
ProviderManager.addExtensionProvider("element", "namespace", new MyExtProvider());

您还可以在特定实例中使用配置文件,并使用 JVM 参数访问它,如以下示例所示:.providersSpring中文文档

-Dsmack.provider.file=file:///c:/my/provider/mycustom.providers

该文件可能如下所示:mycustom.providersSpring中文文档

<?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 库为此目的提供了。 默认情况下,它使用资源将该类注册到类路径中的 jar,如以下 Maven 示例所示:org.jivesoftware.smackx.gcm.provider.GcmExtensionProvidersmack-experimentalexperimental.providersSpring中文文档

<!-- GCM JSON payload -->
<extensionProvider>
    <elementName>gcm</elementName>
    <namespace>google:mobile:data</namespace>
    <className>org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider</className>
</extensionProvider>

此外,还允许目标消息传递协议解析传入数据包并构建传出数据包,如以下示例所示:GcmPacketExtensionSpring中文文档

GcmPacketExtension gcmExtension = (GcmPacketExtension) xmppMessage.getExtension(GcmPacketExtension.NAMESPACE);
String message = gcmExtension.getJson());
GcmPacketExtension packetExtension = new GcmPacketExtension(gcmJson);
Message smackMessage = new Message();
smackMessage.addExtension(packetExtension);

有关详细信息,请参阅本章前面的入站消息通道适配器出站消息通道适配器Spring中文文档