# XMPP 支持 ## XMPP 支持 Spring 集成为[XMPP](https://www.xmpp.org)提供通道适配器。XMPP 代表“可扩展消息传递和存在协议”。 XMPP 描述了在分布式系统中多个代理相互通信的一种方式。典型的用例是发送和接收聊天消息,尽管 XMPP 可以(并且正在)用于其他类型的应用程序。XMPP 描述了参与者的网络。在该网络中,演员可以直接相互联系,并改变广播状态(例如“存在”)。 XMPP 提供的消息架构是世界上一些最大的即时消息网络的基础,包括 Google Talk(Gtalk,也可在 Gmail 中使用)和 Facebook Chat。许多好的开源 XMPP 服务器都是可用的。两种流行的实现方式是[Openfire](https://www.igniterealtime.org/projects/openfire/)和[ejabberd](https://www.ejabberd.im)。 Spring 集成通过提供 XMPP 适配器来提供对 XMPP 的支持,该适配器支持发送和接收 XMPP 聊天消息以及来自客户端花名册中其他条目的存在变化。 你需要在项目中包含此依赖项: Maven ``` org.springframework.integration spring-integration-xmpp 5.5.9 ``` Gradle ``` compile "org.springframework.integration:spring-integration-xmpp:5.5.9" ``` 与其他适配器一样,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 连接,你可以使用名称空间的便利,如下例所示: ``` ``` | |为了更方便起见,你可以依赖默认的命名约定,并省略`id`属性。
此连接使用的默认名称(`xmppConnection`) Bean。| |---|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 如果 XMPP 连接失效,只要上一个连接状态被记录(经过身份验证),就会使用自动登录进行重新连接尝试。我们还注册了`ConnectionListener`,如果启用了`DEBUG`日志级别,它将记录连接事件。 `subscription-mode`属性启动 Rose Listener 来处理来自其他用户的订阅。此功能并不总是适用于目标 XMPP 服务器。例如,Google Cloud Messaging 和 Firebase Cloud Messaging 完全禁用了它。要关闭订阅的花名册侦听器,你可以在使用 XML 配置(`subscription-mode=""`)时使用空字符串进行配置,或者在使用 Java 配置时使用`XmppConnectionFactoryBean.setSubscriptionMode(null)`进行配置。这样做也会在登录阶段禁用花名册。有关更多信息,请参见[`Roster.setRosterLoadedAtLogin(boolean)`](https://download.igniterealtime.org/smack/DOCS/latest/javadoc/org/jivesoftware/smack/roster/roster.html#setrosterloadedatlogin-boolean-)。 ### XMPP 消息 Spring 集成为发送和接收 XMPP 消息提供了支持。为了接收它们,它提供了一个入站消息通道适配器。对于发送它们,它提供了一个出站消息通道适配器。 #### 入站消息通道适配器 Spring 集成适配器支持从系统中的其他用户接收聊天消息。为此,入站消息通道适配器代表用户“登录”,并接收发送给该用户的消息。然后将这些消息转发到你的 Spring 集成客户端。`inbound-channel-adapter`元素为 XMPP 入站消息通道适配器提供配置支持。下面的示例展示了如何配置它: ``` ``` 除了通常的属性(对于消息通道适配器),这个适配器还需要对 XMPP 连接的引用。 XMPP 入站适配器是事件驱动的,并且是`Lifecycle`实现。启动时,它会注册一个`PacketListener`,用于侦听传入的 XMPP 聊天消息。它将接收到的任何消息转发给底层适配器,后者将它们转换为 Spring 集成消息,并将它们发送到指定的`channel`。当停止时,它将注销`PacketListener`。 从版本 4.3 开始,`ChatMessageListeningEndpoint`(及其``)支持在提供的`org.jivesoftware.smack.filter.StanzaFilter`上注册一个`org.jivesoftware.smack.filter.StanzaFilter`的注入,以及一个内部`StanzaListener`实现。有关更多信息,请参见[Javadoc](https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/XMPPConnection.html#addAsyncStanzaListener%28org.jivesoftware.smack.StanzaListener,%20org.jivesoftware.smack.filter.StanzaFilter%29)。 版本 4.3 为`ChatMessageListeningEndpoint`引入了`payload-expression`属性。传入的`org.jivesoftware.smack.packet.Message`表示求值上下文的根对象。当你使用[XMPP 扩展](#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 出站消息通道适配器提供配置支持。 ``` ``` 适配器期望其输入(至少)是类型`java.lang.String`的有效负载和`XmppHeaders.CHAT_TO`的标头值,该值指定消息应发送给哪个用户。要创建消息,你可以使用类似于以下内容的 Java 代码: ``` Message xmppOutboundMsg = MessageBuilder.withPayload("Hello, XMPP!" ) .setHeader(XmppHeaders.CHAT_TO, "userhandle") .build(); ``` 你还可以通过使用 XMPP Header-Enricher 支持来设置 header,如下例所示: ``` ``` 从版本 4.3 开始,包扩展支持已添加到`ChatMessageSendingMessageHandler`(在 XML 配置中是``)。随着常规的`String`和`org.jivesoftware.smack.packet.Message`有效负载,现在可以发送有效负载为`org.jivesoftware.smack.packet.ExtensionElement`(填充到`org.jivesoftware.smack.packet.Message.addExtension()`)而不是`setBody()`的消息。为了方便起见,我们为`ChatMessageSendingMessageHandler`添加了一个`extension-provider`选项。它允许你注入`org.jivesoftware.smack.provider.ExtensionElementProvider`,从而在运行时针对有效负载构建`ExtensionElement`。在这种情况下,有效负载必须是 JSON 或 XML 格式的字符串,这取决于 XEP 协议。 ### XMPP 存在 XMPP 还支持广播状态。你可以利用这种能力,让那些把你列入名册的人看到你的状态变化。这种情况经常发生在你的即时通讯客户身上。你改变你的离开状态并设置一个离开消息,每个在他们的花名册上有你的人看到你的图标或用户名的变化,以反映这个新的状态,并可能看到你的新的“离开”消息。如果你希望接收通知或通知他人状态更改,可以使用 Spring Integration 的“存在”适配器。 #### 入站存在消息通道适配器 Spring 集成提供了一种入站存在消息通道适配器,其支持从系统中在你的花名册上的其他用户接收存在事件。为此,适配器代表用户“登录”,注册`RosterListener`,并将接收到的存在更新事件作为消息转发到由`channel`属性标识的通道。消息的有效负载是`org.jivesoftware.smack.packet.Presence`对象(参见[https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html](https://www.igniterealtime.org/builds/smack/docs/latest/javadoc/org/jivesoftware/smack/packet/Presence.html))。 `presence-inbound-channel-adapter`元素为 XMPP 入站消息通道适配器提供配置支持。以下示例配置了入站存在消息通道适配器: ``` ``` 除了通常的属性外,此适配器还需要对 XMPP 连接的引用。这个适配器是事件驱动的,并且是`Lifecycle`实现。它在启动时注册`RosterListener`,在停止时取消注册`RosterListener`。 #### 出站存在消息通道适配器 Spring 集成还支持发送存在事件,以被网络中碰巧在他们的花名册上有你的其他用户看到。当你向出站存在消息通道适配器发送消息时,它会提取有效负载(预计类型为`org.jivesoftware.smack.packet.Presence`)并将其发送到 XMPP 连接,从而将你的存在事件广告到网络的其余部分。 `presence-outbound-channel-adapter`元素为 XMPP 出站消息通道适配器提供配置支持。下面的示例展示了如何配置出站消息通道适配器: ``` ``` 它也可以是一个轮询消费者(如果它从一个可轮询通道接收消息),在这种情况下,你将需要注册一个 Poller。下面的示例展示了如何做到这一点: ``` ``` 与它的入站对应物一样,它需要对 XMPP 连接的引用。 | |如果你依赖 XMPP 连接 Bean([前面描述的](#xmpp-connection))的默认命名约定,并且在你的应用程序上下文中只配置了一个 XMPP 连接 Bean,那么你可以省略`xmpp-connection`属性。,在这种情况下,
,找到名为`xmppConnection`的 Bean 并将其注入适配器。| |---|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ### 高级配置 Spring Integration 的 XMPP 支持基于 Smack4.0API([https://www.igniterealtime.org/projects/smack/](https://www.igniterealtime.org/projects/smack/)),它允许对 XMPP 连接对象进行更复杂的配置。 由于[更早的陈述](#xmpp-connection),`xmpp-connection`名称空间支持旨在简化基本的连接配置,并且仅支持几个常见的配置属性。然而,`org.jivesoftware.smack.ConnectionConfiguration`对象定义了大约 20 个属性,而为所有这些属性添加名称空间支持并不提供真正的价值。因此,对于更复杂的连接配置,可以将我们的`XmppConnectionFactoryBean`实例配置为常规 Bean,并将`org.jivesoftware.smack.ConnectionConfiguration`作为构造函数参数注入到`FactoryBean`。你可以在`ConnectionConfiguration`实例上直接指定所需的每个属性。( Bean 带有“P”名称空间的定义将很好地工作。)这样,你可以直接设置 SSL(或任何其他属性)。下面的示例展示了如何做到这一点: ``` ``` Smack API 还提供了静态初始化器,这可能会很有帮助。对于更复杂的情况(例如注册 SASL 机制),你可能需要执行某些静态初始化器。其中一个静态初始化器是`SASL认证`,它允许你注册受支持的 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 Reference Manual](https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-java)中的以下部分。 ### XMPP 消息头 Spring 集成 XMPP 适配器自动映射标准 XMPP 属性。默认情况下,这些属性通过使用[`DefaultXmppHeaderMapper`](https://DOCS. Spring.io/ Spring-integration/api/org/springframework/integration/xmpp/support/defaultxmppheadermapper.html)从 Spring integration`MessageHeaders`复制到 Spring integration`MessageHeaders`。 除非`DefaultXmppHeaderMapper`中的`requestHeaderNames`或`replyHeaderNames`属性明确指定,否则不会将任何用户定义的标题复制到或复制到 XMPP 消息。 | |在映射用户定义的标头时,这些值还可以包含简单的通配符模式(例如“thing\*”或“\*thing”)。| |---|----------------------------------------------------------------------------------------------------------------------| 从版本 4.1 开始,`AbstractHeaderMapper`(`DefaultXmppHeaderMapper`的超类)允许你为`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`)不映射`thing1`、`thing2`或`thing3`。该列表确实映射了标准标题加上`thing4`和`qux`。 | |如果你有一个希望映射的以`!`开头的用户定义的头文件,则可以使用`\`将其转义为:`STANDARD_REQUEST_HEADERS,\!myBangHeader`。
在该示例中,标准的请求头文件和`!myBangHeader`文件将被映射。| |---|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| ### XMPP 扩展 Extensions 将“可扩展”放在“可扩展消息传递和存在协议”中。 XMPP 是基于 XML 的,XML 是一种支持命名空间概念的数据格式。通过名称空间,你可以向 XMPP 添加原始规范中未定义的位。XMPP 规范特意只描述了一组核心特性: * 客户机如何连接到服务器 * 加密(SSL/TLS) * Authentication * 服务器如何相互通信以中继消息 * 其他几个基本的构建模块 一旦实现了这一点,就有了一个 XMPP 客户机,可以发送任何类型的数据。然而,你可能需要做的不只是基础工作。例如,你可能需要在消息中包含格式(粗体、斜体等),而核心 XMPP 规范中并未对此进行定义。好吧,你可以想出一种方法来做到这一点,但是,除非其他所有人都像你一样做这件事,否则没有其他软件可以解释它(它们忽略它们无法理解的名称空间)。 为了解决这个问题,XMPP 标准基金会发布了一系列额外的文档,即[XMPP 扩展协议](https://xmpp.org/extensions/xep-0001.html)。通常,每个 XEP 描述一个特定的活动(从消息格式到文件传输、多用户聊天等等)。它们还提供了一种标准格式,供每个人在该活动中使用。 Smack API 通过其`extensions`和`experimental`[projects](https://www.igniterealtime.org/builds/smack/docs/latest/documentation/extensions/index.html)提供了许多 XEP 实现。从 Spring 集成版本 4.3 开始,你可以使用现有 XMPP 通道适配器的任何 XEP。 为了能够处理 XEPS 或任何其他定制的 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`文件可能如下: ``` query jabber:iq:time org.jivesoftware.smack.packet.Time query https://jabber.org/protocol/disco#items org.jivesoftware.smackx.provider.DiscoverItemsProvider subscription https://jabber.org/protocol/pubsub org.jivesoftware.smackx.pubsub.provider.SubscriptionProvider ``` 例如,最流行的 XMPP 消息传递扩展是[谷歌云消息](https://developers.google.com/cloud-messaging/)。Smack 库为此目的提供了`org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider`。默认情况下,它使用`experimental.providers`资源在 Classpath 中用`smack-experimental`jar 注册该类,如下 Maven 示例所示: ``` gcm google:mobile:data org.jivesoftware.smackx.gcm.provider.GcmExtensionProvider ``` 此外,`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)。