|
如需使用最新稳定版本,请使用 Spring Integration 7.0.4! |
XMPP 支持
Spring Integration 为 XMPP 提供了通道适配器。 XMPP 是“可扩展消息与在线状态协议”的缩写。
XMPP 描述了一种在分布式系统中让多个代理相互通信的方式。 其典型用例是发送和接收聊天消息,尽管 XMPP 也可(并且正在)用于其他类型的 applications。 XMPP 描述了一个由参与者组成的网络。 在该网络中,参与者可以直接相互寻址,并广播状态变更(例如“在线状态”)。
XMPP 提供了支撑全球一些最大即时通讯网络的消息传输架构,包括 Google Talk(GTalk,也可在 GMail 内使用)和 Facebook Chat。 许多优秀的开源 XMPP 服务器可供选择。 两个流行的实现是 Openfire 和 ejabberd。
Spring integration 通过提供 XMPP 适配器来支持 XMPP,这些适配器支持从客户端联系人列表中的其他条目发送和接收 XMPP 聊天消息以及状态变更。
您需要将以下依赖项包含到您的项目中:
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-xmpp</artifactId>
<version>6.4.10</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:6.4.10"
与其他适配器一样,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 连接,您可以使用命名空间的便捷功能,如下例所示:
<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 为 payload-expression 属性引入了对 ChatMessageListeningEndpoint 的支持。
传入的 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>)。
除了常规的 String 和 org.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 连接,从而向网络的其余部分通告您的存在事件。
The 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>
与其入站 counterpart 一样,它需要一个对 XMPP Connection 的引用。
如果您依赖 XMPP Connection bean 的默认命名约定(前文所述),并且您的应用上下文中仅配置了一个 XMPP Connection bean,则可以省略 xmpp-connection 属性。
此时,名为 xmppConnection 的 bean 将被定位并注入到适配器中。 |
高级配置
Spring Integration 的 XMPP 支持基于 Smack 4.0 API(www.igniterealtime.org/projects/smack/),它允许对 XMPP Connection 对象进行更复杂的配置。
正如前文所述,xmpp-connection命名空间支持旨在简化基本连接配置,仅支持少数常见的配置属性。
然而,org.jivesoftware.smack.ConnectionConfiguration对象定义了约20个属性,为所有这些属性添加命名空间支持并无实际价值。
因此,对于更复杂的连接配置,您可以将我们的XmppConnectionFactoryBean实例作为普通bean进行配置,并向该FactoryBean注入一个org.jivesoftware.smack.ConnectionConfiguration作为构造函数参数。
您可以在该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 消息头
The Spring Integration XMPP Adapters automatically map standard XMPP properties.
By default, these properties are copied to and from Spring Integration MessageHeaders by using DefaultXmppHeaderMapper.
任何用户定义的头部都不会被复制到 XMPP 消息中,也不会从 XMPP 消息中复制出来,除非通过 requestHeaderNames 或 replyHeaderNames 属性在 DefaultXmppHeaderMapper 中明确指定。
| 在映射用户定义的头部时,值也可以包含简单的通配符模式(例如 "thing*" 或 "*thing")。 |
从 4.1 版本开始,AbstractHeaderMapper(DefaultXmppHeaderMapper 的超类)允许您为 requestHeaderNames 属性配置 NON_STANDARD_HEADERS Tokens(除 STANDARD_REQUEST_HEADERS 之外),以映射所有用户定义的请求头。
The 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 扩展
扩展功能将“可扩展消息与存在协议”中的"Extensible"(可扩展)一词体现得淋漓尽致。
XMPP 基于 XML,这是一种支持命名空间概念的数据格式。 通过命名空间,您可以向 XMPP 添加原始规范中未定义的功能。 XMPP 规范刻意仅描述了一组核心功能:
-
客户端如何连接到服务器
-
加密 (SSL/TLS)
-
身份验证
-
服务器如何相互通信以中继消息
-
其他几个基本构建块
一旦您实现了这一点,您就拥有了一个 XMPP 客户端,可以发送任意类型的数据。 然而,您可能需要超越基础功能。 例如,您可能需要在消息中包含格式(如粗体、斜体等),但这在核心 XMPP 规范中并未定义。 好吧,您可以自行设计一种实现方式,但除非其他人也采用相同的方式,否则其他软件将无法解析它(它们会忽略无法理解的命名空间)。
为了解决该问题,XMPP 标准基金会(XSF)发布了一系列额外文档,称为 XMPP 扩展协议(XEPs)。 通常,每个 XEP 描述一项特定活动(从消息格式化为文件传输、多人聊天等)。 它们还为所有人提供用于该活动的标准格式。
Smack API 在其 extensions 和 experimental 项目中提供了许多 XEP 实现。
从 Spring Integration 4.3 版本开始,您可以使用现有的 XMPP 通道适配器使用任何 XEP。
要能够处理 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
The 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);