邮件支持
本节介绍如何在 Spring Integration 中处理邮件消息。
您需要在项目中包含此依赖项
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mail</artifactId>
<version>6.4.4</version>
</dependency>
compile "org.springframework.integration:spring-integration-mail:6.4.4"
必须通过特定供应商实现来包含 `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)、`from`、`reply-to` 和 `subject`。这些头名称由以下常量定义
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` 接口的一个配置好的实例。有两个实现:`Pop3MailReceiver` 和 `ImapMailReceiver`。实例化其中任何一个的最简单方法是将邮件存储的 'uri' 直接传递给接收器的构造函数,如下例所示
MailReceiver receiver = new Pop3MailReceiver("pop3://usr:pwd@localhost/INBOX");
接收邮件的另一种选项是 IMAP `idle` 命令(如果您的邮件服务器支持)。Spring Integration 提供了 `ImapIdleChannelAdapter`,它本身是一个消息生产端点。它委托给 `ImapMailReceiver` 的一个实例。下一节将介绍如何在 'mail' schema 中使用 Spring Integration 的命名空间支持来配置这两种入站通道适配器。
通常,调用 `IMAPMessage.getContent()` 方法时,会渲染某些头以及正文(对于简单的文本电子邮件),如下例所示 |
To: [email protected]
From: [email protected]
Subject: Test Email
something
对于简单的 `MimeMessage`,`getContent()` 返回邮件正文(上例中的 `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`,如果 `AbstractMailReceiver` 上相应配置了 `shouldDeleteMessages`,则使用 `expunge` 选项关闭文件夹。
从 5.4 版本开始,现在可以不进行任何转换或急切加载内容的情况下,原样返回 `MimeMessage`。此功能通过以下选项组合启用:未提供 `headerMapper`,`simpleContent` 属性为 `false`,且 `autoCloseFolder` 属性为 `false`。在这种情况下,`MimeMessage` 作为 Spring 生成的消息的有效载荷存在。唯一填充的头是上面提到的 `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE`,用于在 `MimeMessage` 处理完成后必须关闭的文件夹。
从 5.5.11 版本开始,如果在没有接收到消息或所有消息都被过滤掉的情况下,即使 `autoCloseFolder` 标志为其他值,文件夹也会在 `AbstractMailReceiver.receive()` 后自动关闭。在这种情况下,下游没有任何内容可用于围绕 `IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE` 头展开可能的逻辑。
从 6.0.5 版本开始,`ImapIdleChannelAdapter` 不再执行异步消息发布。这是必须的,以便阻塞空闲监听器循环以下游处理消息(例如,处理大附件),因为邮件文件夹必须保持打开状态。如果需要异步传递,可以使用 `ExecutorChannel` 作为此通道适配器的输出通道。
入站邮件消息映射
默认情况下,入站适配器生成的消息的有效载荷是原始的 `MimeMessage`。您可以使用该对象来查询头和内容。从 4.3 版本开始,您可以提供一个 `HeaderMapper<MimeMessage>` 将头映射到 `MessageHeaders`。为了方便起见,Spring Integration 为此提供了一个 `DefaultMailHeaderMapper`。它映射以下头
-
`mail_from`:`from` 地址的 `String` 表示。
-
`mail_bcc`:包含 `bcc` 地址的 `String` 数组。
-
`mail_cc`:包含 `cc` 地址的 `String` 数组。
-
`mail_to`:包含 `to` 地址的 `String` 数组。
-
`mail_replyTo`:`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` 的示例包括 `Message` 和 `Multipart`。在这种情况下,`contentType` 头是 `application/octet-stream`。要改变此行为并接收 `Multipart` 对象有效载荷,请在 `MailReceiver` 上将 `embeddedPartsAsBytes` 设置为 `false`。对于 `DataHandler` 未知的内容类型,内容会渲染为 `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 版本开始,当未提供 `headerMapper`,`autoCloseFolder` 为 `false` 且 `simpleContent` 为 `false` 时,`MimeMessage` 会原样返回到 Spring 生成的消息的有效载荷中。这样,`MimeMessage` 的内容会在流程后面被引用时按需加载。上面提到的所有转换仍然有效。
邮件命名空间支持
Spring Integration 提供了用于邮件相关配置的命名空间。要使用它,请配置以下 schema 位置
<?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`,则可以省略 `host`、`username` 和 `mail-sender`。但是,必须使用适当的 JavaMail 属性配置 `host` 和 `username`,例如对于 SMTP
[email protected]
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587
与任何出站通道适配器一样,如果引用的通道是 `PollableChannel`,您应该提供一个 `<poller>` 元素(更多信息请参见 端点命名空间支持)。 |
当您使用命名空间支持时,您还可以使用 `header-enricher` 消息转换器。这样做简化了将前面提到的头应用于发送到邮件出站通道适配器之前的任何消息。
以下示例假设有效载荷是具有指定属性相应 getter 方法的 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>
或者,您可以使用 `value` 属性指定字面值。您还可以指定 `default-overwrite` 和各个 `overwrite` 属性来控制与现有头的行为。
要配置入站通道适配器,您可以在轮询或事件驱动之间选择(假设您的邮件服务器支持 IMAP `idle`——如果不支持,则轮询是唯一的选项)。轮询通道适配器需要存储 URI 和发送入站消息的通道。URI 可以以 `pop3` 或 `imap` 开头。以下示例使用 `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(如果支持)
-
未回复
-
未删除
-
未查看
-
尚未被此邮件接收器处理过(通过使用自定义 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 版本开始,如果指定了 `mail.imap.peek` 或 `mail.imaps.peek` JavaMail 属性,IMAP 邮件接收器会使用它。之前,接收器会忽略该属性,并始终设置 `PEEK` 标志。现在,如果您明确将此属性设置为 `false`,则无论 `shouldMarkMessagesRead` 的设置如何,该消息都会被标记为 `\Seen`。如果未指定,则保留先前的行为(peek 为 `true`)。 |
IMAP `idle` 与连接丢失
使用 IMAP `idle` 通道适配器时,与服务器的连接可能会丢失(例如,由于网络故障),并且由于 JavaMail 文档明确指出实际的 IMAP API 是实验性的,因此在配置 IMAP `idle` 适配器时了解 API 的差异以及如何处理这些差异非常重要。目前,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()` 方法返回 `StoreClosedException` 或 `FolderClosedException`,或者只是返回,从而让您可以继续进行自动重连。目前,自动重连会无限期运行,每十秒尝试一次重连。
在两种配置中,channel 和 should-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
为 true,IMAP 适配器会设置 \Seen
标志。
此外,当电子邮件服务器不支持 \Recent
标志时,只要服务器支持用户标志,IMAP 适配器会用用户标志(默认是 spring-integration-mail-adapter
)标记消息。如果不支持,则将 Flag.FLAGGED
设置为 true
。无论 shouldMarkMessagesRead
设置如何,这些标志都会被应用。但是,从 6.4 版本开始,\Flagged
也可以被禁用。AbstractMailReceiver
暴露了一个 setFlaggedAsFallback(boolean flaggedAsFallback)
选项来跳过设置 \Flagged
。在某些场景下,邮箱中消息上的此类标志是不可取的,即使 \Recent
或用户标志也不支持。
如 SearchTerm
中所述,默认的 SearchTermStrategy
会忽略被如此标记的消息。
从 4.2.2 版本开始,您可以通过在 MailReceiver
上使用 setUserFlag
来设置用户标志的名称。这样做允许多个接收器使用不同的标志(只要邮件服务器支持用户标志)。使用命名空间配置适配器时,可以使用 user-flag
属性。
电子邮件消息过滤
您经常会遇到过滤传入消息的需求(例如,您只想读取 Subject
行包含“Spring Integration”的电子邮件)。您可以通过将入站邮件适配器与基于表达式的 Filter
连接来实现此目的。尽管这样做可行,但这种方法有一个缺点。由于消息将在通过入站邮件适配器后被过滤,所有此类消息都将被标记为已读(SEEN
)或未读(取决于 should-mark-messages-as-read
属性的值)。然而,实际上,只有当消息通过过滤条件时,将其标记为 SEEN
才更有用。这类似于您在电子邮件客户端中滚动浏览预览窗格中的所有消息,但只将实际打开和阅读的消息标记为 SEEN
。
Spring Integration 2.0.4 在 inbound-channel-adapter
和 imap-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
。
事务同步
入站适配器的事务同步允许您在事务提交或回滚后采取不同的行动。您可以通过将 <transactional/>
元素添加到轮询器(用于轮询的 <inbound-adapter/>
)或 <imap-idle-inbound-adapter/>
来启用事务同步。即使没有“真正”的事务涉及,您仍然可以通过在 <transactional/>
元素中使用 PseudoTransactionManager
来启用此功能。更多信息,请参阅 事务同步。
由于邮件服务器不同,特别是某些服务器的限制,目前我们仅为此事务同步提供一种策略。您可以将消息发送到其他 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();
}
}