邮件支持

本节介绍如何在 Spring Integration 中使用邮件消息。

您需要将此依赖项包含到您的项目中

  • Maven

  • Gradle

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

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

邮件发送通道适配器

Spring Integration 为使用 MailSendingMessageHandler 进行出站电子邮件提供支持。它委托给 Spring 的 JavaMailSender 的已配置实例,如下面的示例所示

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

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

MailSendingMessageHandler 具有各种映射策略,这些策略使用 Spring 的 MailMessage 抽象。如果接收到的消息的有效负载已经是 MailMessage 实例,则它将直接发送。因此,我们通常建议您在非平凡的 MailMessage 构造需求之前使用转换器。但是,Spring Integration 支持一些简单的消息映射策略。例如,如果消息有效负载是字节数组,则将其映射到附件。对于简单的基于文本的电子邮件,您可以提供基于字符串的消息有效负载。在这种情况下,将使用该 String 作为文本内容创建 MailMessage。如果您使用消息有效负载类型,其 toString() 方法返回适当的邮件文本内容,请考虑在出站邮件适配器之前添加 Spring Integration 的 ObjectToStringTransformer(有关更多详细信息,请参阅 使用 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 设置为 '[email protected]' 并且提供了 MailHeaders.TO 消息标头,它将优先并覆盖 MailMessage 中的相应值。

邮件接收通道适配器

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: [email protected]
From: [email protected]
Subject: Test Email

something

对于简单的 MimeMessagegetContent() 返回邮件正文(在前面的示例中为 something)。

从 2.2 版开始,框架会急切地获取 IMAP 消息并将它们公开为 MimeMessage 的内部子类。这具有改变 getContent() 行为的不良副作用。这种不一致性在 4.3 版中引入的 邮件映射 增强功能中进一步加剧,因为当提供标头映射器时,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 作为此通道适配器的输出通道。

入站邮件消息映射

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

  • mail_fromfrom 地址的 String 表示形式。

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

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

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

  • mail_replyToreplyTo 地址的 String 表示形式。

  • 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 的示例包括 MessageMultipart。在这种情况下,contentType 标头为 application/octet-stream。要更改此行为并接收 Multipart 对象有效负载,请在 MailReceiver 上将 embeddedPartsAsBytes 设置为 false。对于 DataHandler 未知的 content type,内容将呈现为 byte[],其 contentType 标头为 application/octet-stream

如果您没有提供标题映射器,消息有效负载将是 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 的内容将在稍后流中引用时按需加载。所有上述转换仍然有效。

邮件命名空间支持

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

要配置出站通道适配器,请提供要接收的通道和 MailSender,如下例所示

<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

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

当您使用命名空间支持时,您还可以使用 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 搜索消息,该消息是所有满足以下条件的邮件消息:

  • 是 RECENT(如果支持)

  • 不是 ANSWERED

  • 不是 DELETED

  • 不是 SEEN

  • 尚未由此邮件接收器处理(通过使用自定义 USER 标志或在不支持的情况下简单地使用 NOT FLAGGED 启用)

自定义用户标志是 spring-integration-mail-adapter,但您可以配置它。从 2.2 版开始,ImapMailReceiver 使用的 SearchTerm 可以通过 SearchTermStrategy 完全配置,您可以使用 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"/>

有关消息标记的信息,请参阅 Recent 不受支持时标记 IMAP 消息

重要:IMAP PEEK

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

IMAP idle 和连接丢失

当使用 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 分钟。

您应该了解这些操作(标记邮件为已读和删除邮件)是在邮件被接收后但在邮件被处理之前执行的。这会导致邮件丢失。

您可能希望考虑使用事务同步。请参见 事务同步

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

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

\Recent不支持时标记IMAP消息

如果shouldMarkMessagesAsRead为真,IMAP适配器将设置\Seen标志。

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

null中所述,默认的SearchTermStrategy会忽略带有此类标志的消息。

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

电子邮件消息过滤

通常,您可能会遇到过滤传入消息的需求(例如,您只想阅读主题行中包含“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。答案是否定的。它们不会因另一个标志(RECENT)而受到重复处理,该标志由电子邮件服务器设置,并由 Spring Integration 邮件搜索过滤器使用。文件夹实现设置此标志以指示此消息对于此文件夹是新的。也就是说,它是在上次打开此文件夹后到达的。换句话说,虽然我们的适配器可能会查看电子邮件,但它也会让电子邮件服务器知道该电子邮件已被触碰,因此应由电子邮件服务器标记为 RECENT

事务同步

入站适配器的事务同步允许您在事务提交或回滚后执行不同的操作。您可以通过在轮询的 <inbound-adapter/><imap-idle-inbound-adapter/> 的轮询器中添加 <transactional/> 元素来启用事务同步。即使没有涉及“真实”事务,您仍然可以使用 PseudoTransactionManager<transactional/> 元素一起启用此功能。有关更多信息,请参阅 事务同步

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

<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 类可能的样子

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 配置通道适配器

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