跳到主要内容

邮件支持

QWen Plus 中英对照 Mail Support

邮件支持

本节描述了如何在 Spring Integration 中处理邮件消息。

你需要将这个依赖添加到你的项目中:

<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mail</artifactId>
<version>6.4.2</version>
</dependency>
xml

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

邮件发送通道适配器

Spring Integration 通过 MailSendingMessageHandler 提供对外发电子邮件的支持。它委托给配置好的 Spring 的 JavaMailSender 实例,如下例所示:

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

MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);
java

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

你还可以使用 MessageHeaders 中的某些值来配置 outbound MailMessage。如果可用,值将映射到 outbound mail 的属性,例如收件人(To、Cc 和 BCc)、fromreply-tosubject。标题名称由以下常量定义:

MailHeaders.SUBJECT
MailHeaders.TO
MailHeaders.CC
MailHeaders.BCC
MailHeaders.FROM
MailHeaders.REPLY_TO
java
备注

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

邮件接收通道适配器

Spring Integration 还提供了对入站电子邮件的支持,使用的是 MailReceivingMessageSource。它委托给配置好的 Spring Integration 自己的 MailReceiver 接口的一个实例。有两种实现:Pop3MailReceiverImapMailReceiver。实例化这两种接收器最简单的方法是通过将邮件存储的 'uri' 传递给接收器的构造函数,如下例所示:

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

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

important

通常情况下,当调用 IMAPMessage.getContent() 方法时,某些标题以及正文会被渲染(对于简单的文本邮件),如下例所示:

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

something
none

使用简单的 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();
}
java

保持文件夹打开在解析带有附件的邮件的多部分内容时需要与服务器进行通信的情况下很有用。close()IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 标头委托给 AbstractMailReceiver 使用 expunge 选项关闭文件夹,如果 shouldDeleteMessagesAbstractMailReceiver 上相应地配置。

从 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_from: 发件人地址的 String 表示。

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

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

  • mail_to: 包含收件人地址的 String 数组。

  • mail_replyTo: 回复地址的 String 表示。

  • mail_subject: 邮件主题。

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

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

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

  • mail_expunged: 一个布尔值,指示消息是否已被删除。

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

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

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

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

对于 text/* 类型的电子邮件,有效负载是 String,且 contentType 标头与 mail_contentType 相同。

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

当你不提供标题映射器时,消息有效负载是 jakarta.mail 提供的 MimeMessage。框架提供了一个 MailToStringTransformer,你可以使用它通过策略将邮件内容转换为 String

...
.transform(Mail.toStringTransformer())
...
java

从 4.3 版本开始,转换器处理嵌入式的 Part 实例(以及之前已处理的 Multipart 实例)。该转换器是 AbstractMailTransformer 的子类,它映射前面列表中的地址和主题标题。如果您希望对消息执行其他转换,请考虑继承 AbstractMailTransformer

从 5.4 版本开始,当不提供 headerMapper 时,autoCloseFolderfalsesimpleContentfalseMimeMessage 将原样返回到生成的 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">
xml

要配置一个 outbound 通道适配器,提供接收消息的通道和 MailSender,如下例所示:

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

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

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

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

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

与任何 outbound Channel Adapter 一样,如果引用的通道是 PollableChannel,您应该提供一个 <poller> 元素(请参阅 Endpoint Namespace Support)。

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

以下示例假设有效负载是一个具有适当 getters 的 Java bean,用于获取指定属性,但您可以使用任何 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>
xml

或者,你可以使用 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>
xml

如果你确实有 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"/>
xml

你可以通过创建和填充一个常规的 java.utils.Properties 对象来提供 javaMailProperties —— 例如,通过使用 Spring 提供的 util 命名空间。

important

如果您的用户名包含 @ 字符,请使用 %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>
xml

默认情况下,ImapMailReceiver 根据默认的 SearchTerm 搜索邮件消息,即所有符合以下条件的邮件消息:

  • 是最近的(如果支持)

  • 尚未回答

  • 未被删除

  • 未被查看

  • 尚未被此邮件接收器处理(通过使用自定义 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);

}
java

以下示例依赖于 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"/>
xml

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

important

重要提示: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 或直接返回,从而使您可以继续进行自动重新连接。目前,自动重新连接会无限次运行,每十秒尝试一次重新连接。

important

在这两种配置中,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 不支持此标志,这意味着将其设置为任何值都没有效果,因为消息不会被标记为已读。

在 silent drop 连接的情况下,会在后台定期运行一个空闲取消任务(一个新的 IDLE 通常会立即被处理)。为了控制这个间隔,提供了一个 cancelIdleInterval 选项;默认值为 120 (2 分钟)。RFC 2177 建议间隔不应大于 29 分钟。

important

你应该理解这些操作(标记消息为已读和删除消息)是在消息接收之后但在处理之前执行的。这可能会导致消息丢失。

你可能需要考虑使用事务同步代替。参见 事务同步

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

备注

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

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

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

此外,当邮件服务器不支持 \Recent 标志时,IMAP 适配器会用用户标志标记消息(默认为 spring-integration-mail-adapter),只要服务器支持用户标志即可。如果不支持,Flag.FLAGGED 将被设置为 true。这些标志的设置与 shouldMarkMessagesRead 设置无关。然而,从 6.4 版本开始,也可以禁用 \FlaggedAbstractMailReceiver 提供了一个 setFlaggedAsFallback(boolean flaggedAsFallback) 选项来跳过设置 \Flagged。在某些情况下,在邮箱中的消息上设置这样的标志是不希望的,无论 \Recent 或用户标志是否也不被支持。

SearchTerm 中所述,默认的 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.*'"/>
xml

在前面的例子中,由于 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: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"/>
xml

下面的示例显示了 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);
}
}
java
important

为了在事务之后消息仍然可以被操作,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();
}
}
java