如需使用最新稳定版本,请使用 Spring Integration 7.0.4spring-doc.cadn.net.cn

邮件支持

本节介绍如何在 Spring Integration 中处理邮件消息。spring-doc.cadn.net.cn

您需要将以下依赖项包含到您的项目中:spring-doc.cadn.net.cn

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

The jakarta.mail:jakarta.mail-api must be included via vendor-specific implementation.spring-doc.cadn.net.cn

邮件发送通道适配器

Spring Integration 通过 MailSendingMessageHandler 提供对外部电子邮件的支持。 它将委托给已配置的 Spring JavaMailSender 实例,如下例所示:spring-doc.cadn.net.cn

 JavaMailSender mailSender = context.getBean("mailSender", JavaMailSender.class);

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

MailSendingMessageHandler 具有多种使用 Spring MailMessage 抽象的映射策略。 如果接收到的消息负载已经是 MailMessage 实例,则直接发送。 因此,我们通常建议在非平凡的 MailMessage 构建需求之前为该消费者添加转换器。 然而,Spring Integration 支持几种简单的消息映射策略。 例如,如果消息负载是字节数组,则将其映射为附件。 对于简单的基于文本的电子邮件,您可以提供基于字符串的消息负载。 在这种情况下,将创建一个 MailMessage,并将该 String 作为文本内容。 如果您处理的消息负载类型的 toString() 方法返回适当的邮件文本内容,请考虑在出站邮件适配器之前添加 Spring Integration 的 ObjectToStringTransformer(有关更多详细信息,请参阅 使用 XML 配置转换器)。spring-doc.cadn.net.cn

您还可以使用来自MessageHeaders的某些值配置出站MailMessage。 如果可用,这些值将映射到出站邮件的属性,例如收件人(收件人、抄送和密送)、fromreply-to以及subject。 标题名称由以下常量定义:spring-doc.cadn.net.cn

 MailHeaders.SUBJECT
 MailHeaders.TO
 MailHeaders.CC
 MailHeaders.BCC
 MailHeaders.FROM
 MailHeaders.REPLY_TO
MailHeaders 还允许您覆盖相应的 MailMessage 值。 例如,如果 MailMessage.to 设置为 '[email protected]' 并且提供了 MailHeaders.TO 消息头,则它将具有优先级并覆盖 MailMessage 中的相应值。

邮件接收通道适配器

Spring Integration 还支持通过 MailReceivingMessageSource 进行入站邮件处理。 它委托给已配置的 Spring Integration 自身的 MailReceiver 接口实例。 有两个实现: Pop3MailReceiverImapMailReceiver。 实例化其中任何一个的最简单方法是绕过邮件存储的 'uri',直接将其传递给接收者的构造函数,如下例所示:spring-doc.cadn.net.cn

MailReceiver receiver = new Pop3MailReceiver("pop3://usr:pwd@localhost/INBOX");

接收邮件的另一种方式是使用 IMAP idle 命令(如果您的邮件服务器支持)。 Spring Integration 提供了 ImapIdleChannelAdapter,它本身是一个消息生产端点。 它将委托给一个 ImapMailReceiver 实例。 下一节将展示如何在 'mail' 模式中利用 Spring Integration 的命名空间支持来配置这两种类型的入站通道适配器示例。spring-doc.cadn.net.cn

通常,当调用IMAPMessage.getContent()方法时,某些头部以及正文会被渲染(对于简单的文本电子邮件),如下例所示:spring-doc.cadn.net.cn

To: [email protected]
From: [email protected]
Subject: Test Email

something

使用简单的 MimeMessagegetContent() 返回邮件正文( preceding example 中的 something)。spring-doc.cadn.net.cn

从版本 2.2 开始,框架会急切地获取 IMAP 消息,并将其作为 MimeMessage 的内部子类暴露出来。 这产生了不希望出现的副作用,即改变了 getContent() 的行为。 这种不一致性在版本 4.3 引入的邮件映射增强功能后进一步加剧,因为当提供了头部映射器时,负载(payload)由 IMAPMessage.getContent() 方法渲染。 这意味着 IMAP 内容会根据是否提供了头部映射器而有所不同。spring-doc.cadn.net.cn

从 5.0 版本开始,源自 IMAP 源的消息将始终按照 IMAPMessage.getContent() 的行为渲染内容,无论是否提供了头映射器。 如果您不使用头映射器,并希望回退到仅渲染正文的旧行为,请将邮件接收器的 simpleContent 布尔属性设置为 true。 此属性现在无论是否使用头映射器,都将控制渲染行为。 当提供头映射器时,它现在也允许仅渲染正文。spring-doc.cadn.net.cn

从 5.2 版本开始,邮件接收器提供了 autoCloseFolder 选项。 将其设置为 false 不会在获取后自动关闭文件夹,而是将 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 标头(有关更多信息,请参见 MessageHeaderAccessor API)填充到从通道适配器发送到生产者的每条消息中。 此功能不适用于 Pop3MailReceiver,因为它依赖于打开和关闭文件夹来获取新消息。 目标应用程序有责任在下游流程中必要时调用该标头上的 close()spring-doc.cadn.net.cn

Closeable closeableResource = StaticMessageHeaderAccessor.getCloseableResource(mailMessage);
if (closeableResource != null) {
    closeableResource.close();
}

在解析带有附件的邮件的多部分内容时需要与服务器通信的情况下,保持文件夹打开是有用的。 close() 上的 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 标头会委托 AbstractMailReceiver 使用 expunge 选项关闭文件夹,前提是 AbstractMailReceiver 上分别配置了 shouldDeleteMessagesspring-doc.cadn.net.cn

从版本 5.4 开始,现在可以原样返回 MimeMessage,无需任何转换或急切的内容加载。 此功能通过以下选项组合启用:未提供 headerMappersimpleContent 属性为 falseautoCloseFolder 属性为 falseMimeMessage 作为生成的 Spring 消息的有效载荷存在。 在这种情况下,唯一填充的头部是上述提到的 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE,它对应于文件夹,在处理完 MimeMessage 时必须将其关闭。spring-doc.cadn.net.cn

从版本 5.5.11 开始,如果在 AbstractMailReceiver.receive() 时间内未收到任何消息或所有消息均被过滤(无论 autoCloseFolder 标志如何),该文件夹将自动关闭。 在这种情况下,对于围绕 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 标头的可能逻辑,下游没有任何内容可产生。spring-doc.cadn.net.cn

从版本 6.0.5 开始,ImapIdleChannelAdapter不再执行异步消息发布。 这是必要的,以便阻塞空闲监听器循环以进行下游消息处理(例如带有大附件时),因为邮件文件夹必须保持打开状态。 如果需要异步交接,可以使用ExecutorChannel作为此通道适配器的输出通道。spring-doc.cadn.net.cn

入站邮件消息映射

默认情况下,由入站适配器生成的消息负载是原始的 MimeMessage。 您可以使用该对象来查询头部和内容。 从版本 4.3 开始,您可以提供一个 HeaderMapper<MimeMessage> 将头部映射到 MessageHeaders。 为了方便起见,Spring Integration 为此目的提供了一个 DefaultMailHeaderMapper。 它映射以下头部:spring-doc.cadn.net.cn

当启用消息映射时,负载取决于邮件消息及其实现。 电子邮件内容通常在 DataHandler 内的 MimeMessage 中呈现。spring-doc.cadn.net.cn

对于 text/* 封邮件,负载为 String,且 contentType 头与 mail_contentType 相同。spring-doc.cadn.net.cn

对于包含 jakarta.mail.Part 个嵌入实例的消息,DataHandler 通常呈现为 Part 对象。 这些对象不是 Serializable,不适合使用替代技术(如 Kryo)进行序列化。 因此,默认情况下,当启用映射时,此类负载将呈现为包含 Part 数据的原始 byte[]Part 的示例包括 MessageMultipart。 在此情况下,contentType 头部的值为 application/octet-stream。 若要更改此行为并接收 Multipart 对象负载,请在 MailReceiver 上将 embeddedPartsAsBytes 设置为 false。 对于 DataHandler 未知的内容类型,内容将呈现为带有 contentType 头部且值为 application/octet-streambyte[]spring-doc.cadn.net.cn

当您未提供消息头映射器时,消息负载即为MimeMessage所呈现的内容。jakarta.mail。 框架提供了一个MailToStringTransformer,您可以使用它通过策略将邮件内容转换为Stringspring-doc.cadn.net.cn

   ...
   .transform(Mail.toStringTransformer())
   ...
@Bean
@Transformer(inputChannel="...", outputChannel="...")
public Transformer transformer() {
    return new MailToStringTransformer();
}
   ...
   transform(Mail.toStringTransformer())
   ...
<int-mail:mail-to-string-transformer ... >

从 4.3 版本开始,转换器会处理嵌入的 Part 实例(以及之前已处理的 Multipart 实例)。 该转换器是 AbstractMailTransformer 的子类,用于映射前一个列表中的地址和主题头。 如果您希望对消息执行其他转换,请考虑对 AbstractMailTransformer 进行子类化。spring-doc.cadn.net.cn

从 5.4 版本开始,当未提供 headerMapper 时,autoCloseFolderfalsesimpleContentfalse,则 Spring 消息产生的负载中将原样返回 MimeMessage。 这样,MimeMessage 的内容将在稍后的流程中被引用时按需加载。 上述所有转换仍然有效。spring-doc.cadn.net.cn

邮件命名空间支持

Spring Integration 提供了用于邮件相关配置的命名空间。 要使用它,请配置以下架构位置:spring-doc.cadn.net.cn

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:int-mail="http://www.springframework.org/schema/integration/mail"
  xsi:schemaLocation="http://www.springframework.org/schema/beans
    https://www.springframework.org/schema/beans/spring-beans.xsd
    http://www.springframework.org/schema/integration/mail
    https://www.springframework.org/schema/integration/mail/spring-integration-mail.xsd">

要配置出站通道适配器,请提供用于接收的通道和 MailSender,如下例所示:spring-doc.cadn.net.cn

<int-mail:outbound-channel-adapter channel="outboundMail"
    mail-sender="mailSender"/>

或者,您可以提供主机、用户名和密码,如下例所示:spring-doc.cadn.net.cn

<int-mail:outbound-channel-adapter channel="outboundMail"
    host="somehost" username="someuser" password="somepassword"/>

从版本 5.1.3 开始,如果提供了 java-mail-properties,则 hostusernamemail-sender 可以省略。 然而,hostusername 必须使用适当的 Java Mail 属性进行配置,例如用于 SMTP:spring-doc.cadn.net.cn

[email protected]
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587
与任何出站通道适配器一样,如果引用的通道是 PollableChannel,您应该提供一个 <poller> 元素(参见 端点命名空间支持)。

当您使用命名空间支持时,还可以使用 header-enricher 消息转换器。 这样做可以简化将前述标题应用于任何发送到邮件出站通道适配器的消息的过程。spring-doc.cadn.net.cn

以下示例假设有效负载是一个具有指定属性适当 getter 的 Java Bean,但您也可以使用任何 SpEL 表达式:spring-doc.cadn.net.cn

<int-mail:header-enricher input-channel="expressionsInput" default-overwrite="false">
	<int-mail:to expression="payload.to"/>
	<int-mail:cc expression="payload.cc"/>
	<int-mail:bcc expression="payload.bcc"/>
	<int-mail:from expression="payload.from"/>
	<int-mail:reply-to expression="payload.replyTo"/>
	<int-mail:subject expression="payload.subject" overwrite="true"/>
</int-mail:header-enricher>

或者,您可以使用value属性来指定一个字面量。 您还可以指定default-overwrite和单独的overwrite属性来控制现有标头的行为。spring-doc.cadn.net.cn

要配置入站通道适配器,您可以选择轮询或事件驱动模式(假设您的邮件服务器支持 IMAP idle;如果不支持,则只能使用轮询)。 轮询通道适配器需要存储库 URI 以及用于发送入站消息的通道。 URI 可以以 pop3imap 开头。 以下示例使用了 imap URI:spring-doc.cadn.net.cn

<int-mail:inbound-channel-adapter id="imapAdapter"
      store-uri="imaps://[username]:[password]@imap.gmail.com/INBOX"
      java-mail-properties="javaMailProperties"
      channel="receiveChannel"
      should-delete-messages="true"
      should-mark-messages-as-read="true"
      auto-startup="true">
      <int:poller max-messages-per-poll="1" fixed-rate="5000"/>
</int-mail:inbound-channel-adapter>

如果您确实支持 IMAP idle,您可能希望配置 imap-idle-channel-adapter 元素。 由于 idle 命令启用了事件驱动通知,因此该适配器不需要轮询器。 它在收到新邮件可用的通知后,会立即向指定的通道发送消息。 以下示例配置了一个 IMAP idle 邮件通道:spring-doc.cadn.net.cn

<int-mail:imap-idle-channel-adapter id="customAdapter"
      store-uri="imaps://[username]:[password]@imap.gmail.com/INBOX"
      channel="receiveChannel"
      auto-startup="true"
      should-delete-messages="false"
      should-mark-messages-as-read="true"
      java-mail-properties="javaMailProperties"/>

您可以通过创建并填充一个常规的java.utils.Properties对象来提供javaMailProperties——例如,使用Spring提供的util命名空间。spring-doc.cadn.net.cn

如果您的用户名包含 @ 字符,请使用 %40 代替 @,以避免底层 JavaMail API 的解析错误。

以下示例展示了如何配置一个 java.util.Properties 对象:spring-doc.cadn.net.cn

<util:properties id="javaMailProperties">
  <prop key="mail.imap.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
  <prop key="mail.imap.socketFactory.fallback">false</prop>
  <prop key="mail.store.protocol">imaps</prop>
  <prop key="mail.debug">false</prop>
</util:properties>

默认情况下,ImapMailReceiver 会根据默认的 SearchTerm 搜索邮件消息,这些邮件消息满足以下条件:spring-doc.cadn.net.cn

自定义用户标志为 spring-integration-mail-adapter,但您可以对其进行配置。 从 2.2 版本开始,ImapMailReceiver 使用的 SearchTerm 已完全可通过 SearchTermStrategy 进行配置,您可以通过 search-term-strategy 属性注入它。 SearchTermStrategy 是一个策略接口,包含单个方法,允许您创建由 ImapMailReceiver 使用的 SearchTerm 实例。 以下代码片段展示了 SearchTermStrategy 接口:spring-doc.cadn.net.cn

public interface SearchTermStrategy {

    SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);

}

以下示例依赖于 TestSearchTermStrategy 而不是默认的 SearchTermStrategyspring-doc.cadn.net.cn

<mail:imap-idle-channel-adapter id="customAdapter"
			store-uri="imap:something"
			…
			search-term-strategy="searchTermStrategy"/>

<bean id="searchTermStrategy"
  class="o.s.i.mail.config.ImapIdleChannelAdapterParserTests.TestSearchTermStrategy"/>

有关消息标记的信息,请参阅当不支持Recent时标记 IMAP 消息spring-doc.cadn.net.cn

重要提示:IMAP PEEK

从版本 4.1.1 开始,IMAP 邮件接收器会使用指定的 JavaMail 属性 mail.imap.peekmail.imaps.peek。 此前,接收器会忽略该属性并始终设置 PEEK 标志。 现在,如果您显式将此属性设置为 false,则无论 shouldMarkMessagesRead 的设置如何,消息都将被标记为 \Seen。 如果未指定,则保留之前的行为(peek 为 true)。spring-doc.cadn.net.cn

IMAPidle失去连接

当使用 IMAP idle 通道适配器时,与服务器的连接可能会丢失(例如由于网络故障),而且由于 JavaMail 文档明确指出实际的 IMAP API 是实验性的,因此在配置 IMAP idle 适配器时,了解 API 的差异及其应对方法非常重要。 目前,Spring Integration 邮件适配器已针对 JavaMail 1.4.1 和 JavaMail 1.4.3 进行了测试。 根据所使用的版本,您必须特别注意某些需要设置的 JavaMail 属性,以实现自动重连功能。spring-doc.cadn.net.cn

以下行为在 Gmail 中观察到,但应为您提供一些如何解决与其他提供商重新连接问题的提示。 不过,我们始终欢迎反馈。 再次说明,以下注意事项基于 Gmail。

使用 JavaMail 1.4.1, 如果您将 mail.imaps.timeout 属性设置为一个相对较短的时间段(在我们的测试中约为 5 分钟),IMAPFolder.idle() 会在该超时后抛出 FolderClosedException。然而,如果未设置此属性(它应该是未定义的),则 IMAPFolder.idle() 方法永远不会返回,也永远不会抛出异常。然而,如果连接在短时间内丢失(在我们的测试中为10分钟以内),它会自动重新连接。然而,如果连接丢失了很长时间(超过 10 分钟),IMAPFolder.idle() 不会抛出 FolderClosedException,也不会重新建立连接,而是无限期地保持在阻塞状态,从而使得您在不重启适配器的情况下无法重新连接。因此,使 JavaMail 1.0 重新连接的唯一方法是...4.1 is to set the mail.imaps.timeout property explicitly to some value, but it also means that such value should be relatively short (under 10 min) and the connection should be re-established relatively quickly.同样,对于除 Gmail 之外的其他服务提供商,情况可能有所不同。使用 JavaMail 1.4.3 对 API 进行了重大改进,确保始终存在一种条件,强制 IMAPFolder.idle() 方法返回 StoreClosedExceptionFolderClosedException,或者仅返回,从而让您能够继续进行自动重连。目前,自动重连会无限运行,每隔十秒尝试重新连接一次。spring-doc.cadn.net.cn

在这两种配置中,channelshould-delete-messages都是必需的属性。你应该理解为什么需要should-delete-messages。该问题与 POP3 协议有关,该协议对已读取的消息没有任何感知。它只能知道单个会话内已读取的内容。这意味着,当您的 POP3 邮件适配器运行时,电子邮件会在每次轮询期间作为可用而被成功消费,且任何单封电子邮件消息都不会被投递超过一次。然而,一旦您重启适配器并开始新会话,之前会话中可能已检索到的所有电子邮件消息都会被再次检索。那是 POP3 的特性。有些人可能会认为 should-delete-messages 默认应该是 true。换句话说,存在两种有效且互斥的用途,使得很难选择一个最佳默认值。您可能希望将您的适配器配置为唯一的电子邮件接收器,在这种情况下,您希望能够重启您的适配器,而无需担心之前已发送的消息不会被重复投递。在这种情况下,将 should-delete-messages 设置为 true 是最合理的。然而,您可能还有另一种使用场景,即希望有多个适配器来监控邮件服务器及其内容。换句话说,你希望‘只看不碰’。然后将 should-delete-messages 设置为 false 更为合适。因此,由于很难确定should-delete-messages属性的正确默认值,我们将其设为必须由您设置的必填属性。将其交由您处理也意味着您不太可能遇到非预期的行为。
在配置轮询邮件适配器的should-mark-messages-as-read属性时,您应当注意用于检索消息的协议。 例如,POP3不支持此标志,这意味着将其设置为任意值均无效,因为消息不会被标记为已读。

在连接静默断开的情况下,后台会定期运行一个空闲取消任务(新的 IDLE 通常会立即被处理)。 为了控制此间隔,提供了一个 cancelIdleInterval 选项;默认值为 120(2 分钟)。 RFC 2177 建议间隔不超过 29 分钟。spring-doc.cadn.net.cn

您应该理解,这些操作(标记消息为已读和删除消息)是在消息接收后但在处理前执行的。 这可能导致消息丢失。spring-doc.cadn.net.cn

您可能考虑使用事务同步。 参见 事务同步spring-doc.cadn.net.cn

<imap-idle-channel-adapter/>也接受'error-channel'属性。 如果抛出下游异常且指定了'error-channel',则会向该通道发送一个包含失败消息和原始异常的MessagingException消息。 否则,如果下游通道是同步的,任何此类异常都会被通道适配器记录为警告。spring-doc.cadn.net.cn

从 3.0 版本开始,当发生异常时,IMAP idle 适配器会发出应用程序事件(具体为 ImapIdleExceptionEvent 实例)。 这允许应用程序检测并处理这些异常。 您可以通过使用 <int-event:inbound-channel-adapter> 或任何配置为接收 ImapIdleExceptionEvent 或其父类的 ApplicationListener 来获取这些事件。

标记 IMAP 消息时\Recent不受支持

如果 shouldMarkMessagesAsRead 为 true,则 IMAP 适配器将设置 \Seen 标志。spring-doc.cadn.net.cn

此外,当邮件服务器不支持 \Recent 标志时,IMAP 适配器会为用户标记消息(默认为 spring-integration-mail-adapter),前提是服务器支持用户标志。 如果不支持,则将 Flag.FLAGGED 设置为 true。 这些标志的适用不受 shouldMarkMessagesRead 设置的影响。 然而,从版本 6.4 开始,\Flagged 也可以被禁用。 AbstractMailReceiver 提供了一个 setFlaggedAsFallback(boolean flaggedAsFallback) 选项,用于跳过设置 \Flagged。 在某些场景下,无论是否支持 \Recent 或用户标志,邮箱中消息上的此类标志都是不可取的。spring-doc.cadn.net.cn

正如在 SearchTerm 中讨论的那样,默认的 SearchTermStrategy 会忽略被如此标记的消息。spring-doc.cadn.net.cn

从版本 4.2.2 开始,您可以在 MailReceiver 上使用 setUserFlag 来设置用户标志的名称。 这样做允许多个接收器使用不同的标志(只要邮件服务器支持用户标志)。 当使用命名空间配置适配器时,user-flag 属性可用。spring-doc.cadn.net.cn

邮件消息过滤

通常,您可能会遇到需要过滤传入消息的需求(例如,您只想读取在 Subject 行中包含 'Spring Integration' 的电子邮件)。 您可以通过将入站邮件适配器连接到基于表达式的 Filter 来实现这一点。 虽然这种方法可行,但它存在一个缺点。 由于消息是在通过入站邮件适配器之后才被过滤的,因此所有此类消息都会被标记为已读 (SEEN) 或未读(具体取决于 should-mark-messages-as-read 属性的值)。 然而,实际上更有用的做法是:仅当消息符合过滤条件时,才将其标记为 SEEN。 这类似于在预览窗格中滚动查看所有电子邮件时,仅将实际打开并阅读过的邮件标记为 SEENspring-doc.cadn.net.cn

Spring Integration 2.0.4 在 inbound-channel-adapterimap-idle-channel-adapter 上引入了 mail-filter-expression 属性。 该属性允许您提供一个由 SpEL 和正则表达式组合而成的表达式。 例如,如果您只想读取主题行中包含 'Spring Integration' 的电子邮件,您可以将 mail-filter-expression 属性配置如下:mail-filter-expression="subject matches '(?i).*Spring Integration.*"spring-doc.cadn.net.cn

由于jakarta.mail.internet.MimeMessage是 SpEL 求值上下文的根上下文,因此您可以基于通过MimeMessage可用的任何值进行过滤,包括消息的实际正文。 这一点尤为重要,因为读取消息正文通常会导致这些消息默认被标记为SEEN。 然而,由于我们现在将每条传入消息的PEEK标志设置为'true',因此只有明确标记为SEEN的消息才会被标记为已读。spring-doc.cadn.net.cn

因此,在以下示例中,只有匹配过滤表达式的消息才会被此适配器输出,并且只有这些消息会被标记为已读:spring-doc.cadn.net.cn

<int-mail:imap-idle-channel-adapter id="customAdapter"
	store-uri="imaps://some_google_address:${password}@imap.gmail.com/INBOX"
	channel="receiveChannel"
	should-mark-messages-as-read="true"
	java-mail-properties="javaMailProperties"
	mail-filter-expression="subject matches '(?i).*Spring Integration.*'"/>

在上面的示例中,得益于 mail-filter-expression 属性,只有主题行中包含“Spring Integration”的消息才会由该适配器生成。spring-doc.cadn.net.cn

另一个合理的问题是,在下次轮询或空闲事件时会发生什么,或者当此类适配器重启时会发生什么。 是否会有重复的消息需要过滤?换句话说,如果在上次检索中有五条新消息,只有一条通过了过滤,那么其余四条会怎样处理? 它们会在下次轮询或空闲时再次经过过滤逻辑吗? 毕竟,它们并未被标记为SEEN。 答案是否定的。 它们不会因另一个标志(RECENT)而受到重复处理,该标志由邮件服务器设置,并被 Spring Integration 的邮件搜索过滤器使用。 文件夹实现会设置此标志以表明该消息对于该文件夹是新的。 也就是说,它是在上次打开该文件夹之后才到达的。 换句话说,虽然我们的适配器可以查看邮件,但它也会通知邮件服务器已接触了该邮件,因此应由邮件服务器将其标记为RECENTspring-doc.cadn.net.cn

事务同步

入站适配器的事务同步功能允许您在事务提交或回滚后执行不同的操作。 您可以通过在轮询器中为被轮询的 <transactional/> 元素添加 <inbound-adapter/>,或者在 <imap-idle-inbound-adapter/> 中添加 <inbound-adapter/> 来启用事务同步功能。 即使没有涉及‘真实’的事务,您仍然可以通过使用带有 <transactional/> 元素的 PseudoTransactionManager 来启用此功能。 更多信息,请参见 事务同步spring-doc.cadn.net.cn

由于不同的邮件服务器以及其中一些服务器存在的具体限制,目前我们仅提供针对这些事务同步的策略。 您可以将消息发送到其他 Spring Integration 组件,或调用自定义 Bean 来执行某些操作。 例如,若要在事务提交后将 IMAP 消息移动到不同文件夹,您可能需要使用类似以下的代码:spring-doc.cadn.net.cn

<int-mail:imap-idle-channel-adapter id="customAdapter"
    store-uri="imaps://something.com:[email protected]/INBOX"
    channel="receiveChannel"
    auto-startup="true"
    should-delete-messages="false"
    java-mail-properties="javaMailProperties">
    <int:transactional synchronization-factory="syncFactory"/>
</int-mail:imap-idle-channel-adapter>

<int:transaction-synchronization-factory id="syncFactory">
    <int:after-commit expression="@syncProcessor.process(payload)"/>
</int:transaction-synchronization-factory>

<bean id="syncProcessor" class="thing1.thing2.Mover"/>

以下示例展示了 Mover 类可能的外观:spring-doc.cadn.net.cn

public class Mover {

    public void process(MimeMessage message) throws Exception {
        Folder folder = message.getFolder();
        folder.open(Folder.READ_WRITE);
        String messageId = message.getMessageID();
        Message[] messages = folder.getMessages();
        FetchProfile contentsProfile = new FetchProfile();
        contentsProfile.add(FetchProfile.Item.ENVELOPE);
        contentsProfile.add(FetchProfile.Item.CONTENT_INFO);
        contentsProfile.add(FetchProfile.Item.FLAGS);
        folder.fetch(messages, contentsProfile);
        // find this message and mark for deletion
        for (int i = 0; i < messages.length; i++) {
            if (((MimeMessage) messages[i]).getMessageID().equals(messageId)) {
                messages[i].setFlag(Flags.Flag.DELETED, true);
                break;
            }
        }

        Folder somethingFolder = store.getFolder("SOMETHING");
        somethingFolder.appendMessages(new MimeMessage[]{message});
        folder.expunge();
        folder.close(true);
        somethingFolder.close(false);
    }
}
为了使消息在事务后仍可被操作,should-delete-messages必须设置为'false'。

使用 Java DSL 配置通道适配器

To configure mail component in Java DSL, the framework provides a o.s.i.mail.dsl.Mail factory, which can be used like this:spring-doc.cadn.net.cn

@SpringBootApplication
public class MailApplication {

    public static void main(String[] args) {
        new SpringApplicationBuilder(MailApplication.class)
            .web(false)
            .run(args);
    }

    @Bean
    public IntegrationFlow imapMailFlow() {
        return IntegrationFlow
                .from(Mail.imapInboundAdapter("imap://user:pw@host:port/INBOX")
                            .searchTermStrategy(this::fromAndNotSeenTerm)
                            .userFlag("testSIUserFlag")
                            .simpleContent(true)
                            .javaMailProperties(p -> p.put("mail.debug", "false")),
                    e -> e.autoStartup(true)
                            .poller(p -> p.fixedDelay(1000)))
                .channel(MessageChannels.queue("imapChannel"))
                .get();
    }

    @Bean
    public IntegrationFlow sendMailFlow() {
        return IntegrationFlow.from("sendMailChannel")
                .enrichHeaders(Mail.headers()
                        .subjectFunction(m -> "foo")
                        .from("foo@bar")
                        .toFunction(m -> new String[] { "bar@baz" }))
                .handle(Mail.outboundAdapter("gmail")
                            .port(smtpServer.getPort())
                            .credentials("user", "pw")
                            .protocol("smtp"),
                    e -> e.id("sendMailEndpoint"))
                .get();
    }
}