Mail Support

本部分介绍了如何在 Spring Integration 中处理邮件信息。 你需要将此依赖项包含在你的项目中:

  • Maven

  • Gradle

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

jakarta.mail:jakarta.mail-api 必须通过特定于供应商的实现来包含。

Mail-sending Channel Adapter

Spring Integration 使用 MailSendingMessageHandler 为发件邮件提供支持。它委托给一个配置的 Spring 的 JavaMailSender 实例, 如下所示:

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

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

MailSendingMessageHandler 有多种映射策略,使用 Spring 的 MailMessage 抽象。如果接收到的消息的负载已经是 MailMessage 实例,则直接发送它。因此,我们通常建议在该消费者之前加上一个变压器,以满足非平凡的 MailMessage 构造要求。但是,Spring 集成支持一些简单的消息映射策略。例如,如果消息负载是字节数组,则将其映射到附件。对于基于文本的简单电子邮件,您可以提供基于字符串的消息负载。在这种情况下,将创建一个 MailMessage 并将该 String 作为文本内容。如果您使用的是 toString() 方法返回适当邮件文本内容的消息负载类型,请考虑在出站邮件适配器之前添加 Spring 集成的 ObjectToStringTransformer(有关更多详细信息,请参见 Configuring a Transformer with XML 中的示例)。

您还可以使用 MessageHeaders 中的某些值配置发件 MailMessage。如果有, 这些值会映射到发件邮件的属性, 例如收件人(To、Cc 和 BCc)、fromreply-tosubject。头名称由以下常量定义:

 MailHeaders.SUBJECT
 MailHeaders.TO
 MailHeaders.CC
 MailHeaders.BCC
 MailHeaders.FROM
 MailHeaders.REPLY_TO

MailHeaders 还允许你覆盖相应的 MailMessage 值。例如,如果 MailMessage.to 设置为“ thing1@things.com”,并且提供了 MailHeaders.TO 消息标头,则它将优先并覆盖 MailMessage 中的相应值。

Mail-receiving Channel Adapter

Spring Integration 还使用 MailReceivingMessageSource 为收件邮件提供支持。它委托给 Spring Integration 自身的 MailReceiver 接口的一个配置实例。有两种实现: Pop3MailReceiverImapMailReceiver。实例化其中的任何一种的最简单方法是将邮件存储的 “uri” 绕过接收者的构造函数, 如下所示:

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

另一种接收邮件的选项是 IMAP idle 命令(如果您的邮件服务器支持)。Spring Integration 提供 ImapIdleChannelAdapter, 它本身就是一个消息生成端点。它委托给 ImapMailReceiver 的一个实例。下一部分提供使用 Spring Integration 在 “mail” 模式中使用命名空间支持配置这两种类型的入站通道适配器的示例。

通常, 当调用 IMAPMessage.getContent() 方法时, 某些头以及正文将被渲染(对于一个简单的文本电子邮件), 如下所示:

To: thing1@things.com
From: thing2@morethings.com
Subject: Test Email

something

使用一个简单的 MimeMessage, getContent() 将返回邮件正文(在前一个示例中为 “something”)。

从 2.2 版开始,该框架会急切地获取 IMAP 消息并将它们作为 MimeMessage 的内部子类公开。这导致了改变 getContent() 行为的副作用。由于在 4.3 版中引入了 Mail Mapping 增强,因此这种不一致的情况进一步加剧,因为当提供标题映射器时,负载由 IMAPMessage.getContent() 方法呈现。这意味着 IMAP 内容有所不同,具体取决于是否提供了标题映射器。

从版本 5.0 开始,源自 IMAP 的消息会根据 IMAPMessage.getContent() 行为呈现其内容,无论是否提供了标题映射器。如果你不使用标题映射器,并且希望恢复到仅呈现正文的先前行为,请将邮件接收器上的布尔属性 simpleContent 设置为 true. 无论是否使用标题映射器,此属性现在都控制渲染。现在在提供了标题映射器时它允许仅呈现正文。

从 5.2 版开始,邮件接收器上提供了 autoCloseFolder 选项。将其设置到 false 不会在获取后自动关闭文件夹,而是为通道适配器发送的每条消息填充一个 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 标头(有关更多详细信息,请参见 MessageHeaderAccessor API)。这无法与 Pop3MailReceiver 一起使用,因为它依赖于打开和关闭文件夹以获取新消息。在下游流中每当需要时,目标应用程序负责调用此标头上的 close()

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

在使用附件解析电子邮件的多部分内容期间需要与服务器通信时,保持文件夹处于打开状态非常有用。 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 头上的 close() 委托给 AbstractMailReceiver ,以使用 expunge 选项关闭文件夹,如果在 AbstractMailReceiver 上分别配置了 shouldDeleteMessages

从 5.4 版本开始,现在可以返回一个 MimeMessage 原样,没有任何转换或热切内容加载。通过以下选项组合启用了此功能:未提供 headerMappersimpleContent 属性为 falseautoCloseFolder 属性为 falseMimeMessage 作为所生成 Spring 消息的有效载荷存在。在这种情况下,填充的唯一头是上面提到的用于文件夹的 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE ,在 MimeMessage 处理完成时必须关闭文件夹。

从 5.5.11 版本开始,如果未收到消息或所有消息均被过滤掉,则在 AbstractMailReceiver.receive() 之后自动关闭文件夹,而与 autoCloseFolder 标志无关。在这种情况下,对于 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 头周围的可能逻辑,下游没有任何要生成的内容。

从 6.0.5 版本开始, ImapIdleChannelAdapter 不再执行异步消息发布。这对于阻塞空闲侦听器循环以便在消息下游进行处理(例如,有大附件)是必需的,因为邮件文件夹必须保持打开状态。如果需要异步切换,可以使用 ExecutorChannel 作为该信道适配器的输出信道。

Inbound Mail Message Mapping

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

  • mail_from: `String`地址的 `from`表示形式。

  • mail_bcc:包含 `bcc`地址的 `String`数组。

  • mail_cc:包含 `cc`地址的 `String`数组。

  • mail_to:包含 `to`地址的 `String`数组。

  • mail_replyTo: `replyTo`地址的 `String`表示形式。

  • mail_subject: The mail subject.

  • mail_lineCount:行数(如果可用)。

  • mail_receivedDate:接收日期(如果可用)。

  • mail_size:邮件大小(如果可用)。

  • mail_expunged:指示邮件是否已删除的布尔值。

  • mail_raw:包含所有邮件头及其值的 MultiValueMap

  • mail_contentType: 原始邮件消息的内容类型。

  • contentType: 有效负载内容类型(见下文)。

启用消息映射时,有效载荷取决于邮件消息及其实现。电子邮件内容通常由 MimeMessage 中的 DataHandler 呈现。

对于 text/* 类型的电子邮件,有效载荷是一个 StringcontentType 头与 mail_contentType 相同。

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

当你未提供头映射器时,消息有效载荷是 jakarta.mail 呈现的 MimeMessage 。该框架提供了一个 MailToStringTransformer ,你可以使用它通过使用战略将邮件内容转换为 String 来转换消息:

  • Java DSL

  • Java

  • Kotlin

  • XML

   ...
   .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

从 5.4 版本开始,当未提供 headerMapperautoCloseFolderfalsesimpleContentfalse 时,返回的 MimeMessage 在所生成 Spring 消息的有效载荷中保持原样。这样,当稍后在流中引用 MimeMessage 的内容时,将按需加载。上述所有转换仍然有效。

Mail Namespace Support

Spring Integration 为与邮件相关的配置提供了一个命名空间。要使用它,请配置以下架构位置:

<?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">

要配置出站信道适配器,请提供从其接收的信道和邮件发送器,如下例所示:

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

或者,你可以提供主机、用户名和密码,如下例所示:

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

从 5.1.3 版本开始,如果提供了 java-mail-properties ,则可以省略 hostusernamemail-sender 。然而,必须使用适当的 Java 邮件属性配置 hostusername ,例如对于 SMTP:

mail.user=someuser@gmail.com
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587

与任何出站通道适配器一样,如果引用的通道是 PollableChannel,你应该提供一个 <poller> 元素(参见 Endpoint Namespace Support)。

当你使用命名空间支持时,还可以使用 header-enricher 消息变换器。这样做简化了在发送到邮件出站信道适配器之前将前面提到的头应用于任何消息。

以下示例假设有效载荷是 Java bean,且为指定属性提供了适当的 getter,但你可以使用任何 SpEL 表达式:

<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 属性,以控制现有标头的行为。

若要配置入站通道适配器,你可以选择轮询或事件驱动(假设你的邮件服务器支持 IMAP idle — 如果不支持,则轮询是唯一选项)。轮询通道适配器需要存储的 URI 以及用于发送入站邮件的通道。URI 可以以 pop3imap 开头。以下示例使用 imap URI:

<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 邮件通道:

<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 命名空间。

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

以下示例演示如何配置 java.util.Properties 对象:

<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 查找邮件,这是所有邮件:

  • Are RECENT (if supported)

  • Are NOT ANSWERED

  • Are NOT DELETED

  • Are NOT SEEN

  • 未对此邮件接收器进行处理(通过使用自定义 USER 标志启用,或者如果不受支持,则只是 NOT FLAGGED)

自定义用户标志是 spring-integration-mail-adapter,但你可以对其进行配置。自 2.2 版本以来,ImapMailReceiver 使用的 SearchTermSearchTermStrategy 完全配置,你可以使用 search-term-strategy 属性注入该策略。SearchTermStrategy 是一个策略接口,具有让你创建 ImapMailReceiver 使用的 SearchTerm 实例的单个方法。以下清单显示了 SearchTermStrategy 接口:

public interface SearchTermStrategy {

    SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);

}

以下示例依赖于 TestSearchTermStrategy 而不是默认的 SearchTermStrategy

<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"/>

有关消息标记的信息,请参见 Marking IMAP Messages When Recent Is Not Supported

Example 1. Important: IMAP PEEK

从 4.1.1 版开始,IMAP 邮件接收器将使用 mail.imap.peekmail.imaps.peek JavaMail 属性,如果指定了该属性。以前,接收器会忽略该属性并始终设置 PEEK 标志。现在,如果你明确将此属性设置为 false,则无论 shouldMarkMessagesRead 设置如何,邮件都会被标记为 \Seen。如果没有指定,则保留以前的的行为(窥视为 true)。

IMAP idle and Lost Connections

使用 IMAP idle 通道适配器时,与服务器的连接可能会丢失(例如,通过网络故障),并且由于 JavaMail 文档明确指出实际 IMAP API 是实验性的,因此了解 API 中的差异以及如何配置 IMAP idle 适配器时处理这些差异非常重要。目前,Spring Integration 邮件适配器已使用 JavaMail 1.4.1 和 JavaMail 1.4.3 进行了测试。根据使用哪一个版本,你必须特别注意一些需要设置的 JavaMail 属性以便于自动重新连接。

在 Gmail 中观察到了以下行为,但应该提供一些提示,说明如何解决与其他提供商重新连接的问题。但是,总是欢迎反馈意见。同样地,以下注释基于 Gmail。

使用 JavaMail 1.4.1 时,如果你将 mail.imaps.timeout 属性设置为相对较短的时间段(在我们的测试中大约为 5 分钟),IMAPFolder.idle() 会在此超时后抛出 FolderClosedException。但是,如果未设置此属性(它应该是无限期的),则 IMAPFolder.idle() 方法永远不会返回,也不会抛出异常。但是,如果连接在短时间内丢失(在我们的测试中不到 10 分钟),它会自动重新连接。但是,如果连接长时间丢失(超过 10 分钟),IMAPFolder.idle() 不会抛出 FolderClosedException,也不会重新建立连接,并且会无限期地保持在阻塞状态,从而让你无法在不重新启动适配器的情况下重新连接。因此,在 JavaMail 1.4.1 中使重新连接工作的方法是将 mail.imaps.timeout 属性显式设置为某个值,但这也意味着该值应该相对较短(不到 10 分钟),并且应该相对较快地重新建立连接。再说一遍,这对于除 Gmail 之外的提供商来说可能有所不同。JavaMail 1.4.3 对 API 进行了重大改进,确保始终有一个条件强制 IMAPFolder.idle() 方法返回 StoreClosedExceptionFolderClosedException,或者简单返回,从而让你可以继续进行自动重新连接。目前,自动重新连接无限运行,每十秒尝试重新连接一次。

在这两种配置中,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 分钟。

你应该明白,这些操作(标记邮件为已读和删除邮件)是在邮件被接收后但在被处理之前执行的。这可能导致丢失邮件。 您可能希望考虑改用事务同步。请参阅 Transaction Synchronization

<imap-idle-channel-adapter/> 也接受 error-channel 属性。如果抛出下游异常并指定了 error-channel,则会将包含失败消息和原始异常的 MessagingException 消息发送到此通道。否则,如果下游通道是同步的,则通道适配器会将任何此类异常记录为警告。

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

Marking IMAP Messages When \Recent Is Not Supported

如果 shouldMarkMessagesAsRead 为 true,则 IMAP 适配器会设置 \Seen 标志。

另外,当电子邮件服务器不支持 \Recent 标志时,IMAP 适配器会使用用户标志(默认情况下为 spring-integration-mail-adapter)标记邮件,只要服务器支持用户标志。如果不支持,则将 Flag.FLAGGED 设置为 true。这些标志会应用,而无论 shouldMarkMessagesRead 设置如何。

正如 null 中所讨论的,默认 SearchTermStrategy 会忽略标记为如此的消息。

从 4.2.2 版本开始,您可以通过 MailReceiver 中启用 setUserFlag 来设置用户标记的名称。这样做可以使多个接收者使用不同的标记(只要邮件服务器支持用户标记)。配置适配器时,user-flag 属性可以在命名空间中使用。

Email Message Filtering

很多情况下,您可能需要对传入的消息进行筛选(比如,您只想读取 Subject 行中包含“Spring Integration”的电子邮件)。可以通过将入站邮件适配器与基于表达式的 Filter 相连接实现这一目标。虽然这种方法可以实现目标,但它也有一定弊端。由于消息是在经过入站邮件适配器后进行筛选的,因此所有此类消息都被标记为已读 (SEEN) 或未读(取决于 should-mark-messages-as-read 属性的值)。然而,实际上,仅当消息通过筛选条件时将其标记为 SEEN 会更有用。这类似于在预览窗格中滚动查看所有电子邮件时查看电子邮件客户端,但仅将实际上打开和标记为 SEEN 的消息标记为已读。

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.'

由于 jakarta.mail.internet.MimeMessage 是 SpEL 评估上下文的根上下文,您可以在任何可通过 MimeMessage 获得的值(包括消息的实际内容)的基础上进行筛选。这个功能尤其重要,因为读取消息内容通常会导致此类消息默认被标记为 SEEN。不过,由于我们现在将所有传入消息的 PEEK 标志设置为“true”,因此只有明确标记为 SEEN 的消息才会被标记为已读。

因此,在以下示例中,只有与筛选表达式匹配的消息才会由此适配器输出,并且只有这些消息会被标记为已读:

<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”的消息。

另一个合理的问题是下次轮询或空闲事件发生时会发生什么,或者在重新启动此类适配器时会发生什么。是否会出现要筛选的消息重复的问题?换句话说,如果您在上次检索中有五条新消息且只有一条通过了筛选,其他四条消息会怎样?它们是否会在下次轮询或空闲时再次通过筛选逻辑?毕竟它们并没有被标记为 SEEN。答案是否定的。它们不会因为由电子邮件服务器设置并由 Spring Integration 邮件搜索筛选器使用的另一个标记 (RECENT) 而受到重复处理。文件夹实现设置此标记以表明此消息对该文件夹而言是新的。也就是说,自上次打开此文件夹以来,此消息已到达。换句话说,虽然我们的适配器可能会窥视电子邮件,但它也会让电子邮件服务器知道已触及此电子邮件,因此电子邮件服务器会将其标记为 RECENT

Transaction Synchronization

入站适配器的交易同步让您可以在交易提交或回滚后采取不同的操作。您可以通过将 <transactional/> 元素添加到轮询 <inbound-adapter/><imap-idle-inbound-adapter/> 的轮询器,来启用交易同步。即使没有涉及“实际”事务,您仍然可以通过使用包含 <transactional/> 元素的 PseudoTransactionManager 来启用此功能。有关详细信息,请参见 Transaction Synchronization

由于有不同的邮件服务器,特别是某些服务器存在限制,因此我们目前仅为这些事务同步提供策略。您可以将消息发送给其他某些 Spring Integration 组件或调用自定义 Bean 以执行一些操作。例如,要在事务提交后将 IMAP 消息移到其他文件夹,您可以使用类似以下内容:

<int-mail:imap-idle-channel-adapter id="customAdapter"
    store-uri="imaps://something.com:password@imap.something.com/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 类的外观:

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”。

Configuring channel adapters with the Java DSL

要在 Java DSL 中配置邮件组件,该框架提供了一个 o.s.i.mail.dsl.Mail 工厂,其使用方法如下:

@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();
    }
}