XMPP 支持
XMPP 支持
Spring Integration 为 XMPP 提供了通道适配器。XMPP 代表 “可扩展的消息和状态协议” 。
XMPP 描述了在分布式系统中多个代理相互通信的方式。典型的用例是发送和接收聊天消息,尽管 XMPP 可以(并且已经被)用于其他类型的应用程序。XMPP 描述了一个参与者网络。在这个网络中,参与者可以互相直接通信并广播状态变化(例如“在线状态”)。
Spring 集成通过提供 XMPP 适配器来支持 XMPP,这些适配器支持发送和接收客户端花名册中其他条目的 XMPP 聊天消息和状态变化。
你需要将这个依赖添加到你的项目中:
- Maven
- Gradle
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-xmpp</artifactId>
    <version>6.4.2</version>
</dependency>
compile "org.springframework.integration:spring-integration-xmpp:6.4.2"
与其他适配器一样,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 属性。默认名称 (xmppConnection) 用于此连接bean。
如果 XMSS 连接变为空闲,只要之前的连接状态是已记录(已认证),就会尝试重新连接并自动登录。我们还注册了一个 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,该过滤器将与内部 StanzaListener 实现一起注册到提供的 XMPPConnection 上。更多信息请参阅 Javadoc。
版本 4.3 为 ChatMessageListeningEndpoint 引入了 payload-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]"
外发消息通道适配器
你也可以通过使用 outbound message channel 适配器向 XMPP 上的其他用户发送聊天消息。outbound-channel-adapter 元素为 XMPP outbound message channel 适配器提供配置支持。
<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 header-enricher 支持来设置标头,如下例所示:
<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>)增加了对数据包扩展的支持。除了常规的 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 也支持广播状态。你可以使用此功能,让人们在他们的联系人列表中有你时可以看到你的状态变化。这在你的即时通讯客户端中经常发生。你更改了离开状态并设置了离开消息,所有在联系人列表中有你的人会看到你的图标或用户名发生变化以反映这种新状态,并且可能会看到你的新“离开”消息。如果你想接收通知或通知其他人状态变化,可以使用 Spring Integration 的“presence”适配器。
入站 Presence 消息通道适配器
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>
与其入站 counterpart 一样,它需要引用一个 XMPP 连接。
如果你依赖于 XMPP 连接 bean 的默认命名约定 (之前描述的) 并且在你的应用程序上下文中只配置了一个 XMPP 连接 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,并将 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 的。
任何用户定义的标题都不会复制到或从 XMPP 消息中复制,除非由 DefaultXmppHeaderMapper 的 requestHeaderNames 或 replyHeaderNames 属性明确指定。
在映射用户定义的标头时,值也可以包含简单的通配符模式(例如 "thing*" 或 "*thing")。
从 4.1 版本开始,AbstractHeaderMapper(DefaultXmppHeaderMapper 的一个超类)允许你配置 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 这样的列表不会映射 thing1、thing2 或 thing3。该列表确实会映射标准头加上 thing4 和 qux。
如果你有一个以 ! 开头的自定义头部并且你希望映射它,可以使用 \ 来转义它,例如:STANDARD_REQUEST_HEADERS,\!myBangHeader。在那个例子中,标准请求头部和 !myBangHeader 都会被映射。
XMPP 扩展
扩展为“可扩展消息和状态协议”中的“可扩展”提供了实现。
XMPP 是基于 XML 的,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
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);