此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Integration 6.3.1Spring中文文档

此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Integration 6.3.1Spring中文文档

本节介绍如何在 Spring Integration 中使用邮件。Spring中文文档

您需要将此依赖项包含在项目中:Spring中文文档

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

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

邮件发送通道适配器

Spring Integration 使用 . 它委托给 Spring 的已配置实例,如以下示例所示:MailSendingMessageHandlerJavaMailSenderSpring中文文档

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

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

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

您还可以使用中的某些值配置出站。 如果可用,则值将映射到出站邮件的属性,例如收件人(收件人、抄送和 BCc)、、 和 . 标头名称由以下常量定义:MailMessageMessageHeadersfromreply-tosubjectSpring中文文档

 MailHeaders.SUBJECT
 MailHeaders.TO
 MailHeaders.CC
 MailHeaders.BCC
 MailHeaders.FROM
 MailHeaders.REPLY_TO
MailHeaders还允许您覆盖相应的值。 例如,如果设置为“[email protected]”并且提供了邮件头,则该邮件头优先并覆盖 .MailMessageMailMessage.toMailHeaders.TOMailMessage
MailHeaders还允许您覆盖相应的值。 例如,如果设置为“[email protected]”并且提供了邮件头,则该邮件头优先并覆盖 .MailMessageMailMessage.toMailHeaders.TOMailMessage

邮件接收通道适配器

Spring Integration 还支持带有 . 它委托给Spring Integration自己的接口的配置实例。 有两种实现:和 。 实例化其中任何一个的最简单方法是绕过邮件存储的“uri”到接收方的构造函数,如以下示例所示:MailReceivingMessageSourceMailReceiverPop3MailReceiverImapMailReceiverSpring中文文档

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

接收邮件的另一个选项是 IMAP 命令(如果邮件服务器支持)。 Spring Integration 提供了 ,它本身就是一个消息生成端点。 它委托给 . 下一节将介绍如何在“mail”架构中使用Spring Integration的命名空间支持配置两种类型的入站通道适配器。idleImapIdleChannelAdapterImapMailReceiverSpring中文文档

通常,调用该方法时,会呈现某些标头和正文(对于简单的文本电子邮件),如以下示例所示:IMAPMessage.getContent()Spring中文文档

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

something

使用简单的 ,返回邮件正文(在前面的示例中)。MimeMessagegetContent()somethingSpring中文文档

从版本 2.2 开始,框架急切地获取 IMAP 消息,并将它们公开为 的内部子类。 这产生了改变行为的不良副作用。 版本 4.3 中引入的邮件映射增强功能进一步加剧了这种不一致,因为当提供标头映射器时,有效负载是由该方法呈现的。 这意味着 IMAP 内容会有所不同,具体取决于是否提供了标头映射器。MimeMessagegetContent()IMAPMessage.getContent()Spring中文文档

从 5.0 版开始,无论是否提供标头映射器,源自 IMAP 源的邮件都会根据行为呈现内容。 如果不使用标头映射器,并且希望恢复到以前仅呈现正文的行为,请将邮件接收方上的布尔属性设置为 。 此属性现在控制呈现,而不管是否使用标头映射器。 现在,当提供标头映射器时,它允许仅进行正文呈现。IMAPMessage.getContent()simpleContenttrueSpring中文文档

从版本 5.2 开始,邮件接收器上提供了该选项。 将其设置为不会在提取后自动关闭文件夹,而是将标头(有关详细信息,请参阅 MessageHeaderAccessor API)填充到从通道适配器发送给生产者的每封邮件中。 这不起作用,因为它依赖于打开和关闭文件夹来获取新邮件。 目标应用程序负责在下游流中需要时调用此标头:autoCloseFolderfalseIntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEPop3MailReceiverclose()Spring中文文档

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

在解析带有附件的电子邮件的多部分内容期间需要与服务器通信时,保持文件夹处于打开状态非常有用。 on 标头委托给 to close the folder with 选项 if 分别配置在 .close()IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEAbstractMailReceiverexpungeshouldDeleteMessagesAbstractMailReceiverSpring中文文档

从版本 5.4 开始,现在可以按原样返回,而无需任何转换或急切的内容加载。 此功能通过以下选项组合启用:no provided,属性为 ,属性为 。 作为生成的 Spring 消息的有效负载存在。 在这种情况下,填充的唯一标题是上面提到的文件夹,在处理完成时必须关闭该文件夹。MimeMessageheaderMappersimpleContentfalseautoCloseFolderfalseMimeMessageIntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEMimeMessageSpring中文文档

从版本 5.5.11 开始,如果未收到任何邮件或所有邮件都独立于标志过滤掉,则该文件夹将自动关闭。 在这种情况下,对于围绕标头的可能逻辑,没有什么可以生成下游的。AbstractMailReceiver.receive()autoCloseFolderIntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCESpring中文文档

从版本 6.0.5 开始,不再执行异步消息发布。 这对于阻止下游邮件处理的空闲侦听器循环(例如,使用大附件)是必要的,因为邮件文件夹必须保持打开状态。 如果需要异步切换,则可以将 an 用作此通道适配器的输出通道。ImapIdleChannelAdapterExecutorChannelSpring中文文档

通常,调用该方法时,会呈现某些标头和正文(对于简单的文本电子邮件),如以下示例所示:IMAPMessage.getContent()Spring中文文档

入站邮件映射

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

启用邮件映射后,有效负载取决于邮件及其实现。 电子邮件内容通常由 .DataHandlerMimeMessageSpring中文文档

对于电子邮件,有效负载为 a,标头与 相同。text/*StringcontentTypemail_contentTypeSpring中文文档

对于具有嵌入式实例的消息,通常会呈现一个对象。 这些对象不适合也不适合使用替代技术(如 )进行序列化。 因此,默认情况下,启用映射时,此类有效负载将呈现为包含数据的原始负载。 的示例是 和 。 在这种情况下,标头是。 要更改此行为并接收对象有效负载,请设置为 on 。 对于 未知的内容类型,内容将呈现为标头为 的 。jakarta.mail.PartDataHandlerPartSerializableKryobyte[]PartPartMessageMultipartcontentTypeapplication/octet-streamMultipartembeddedPartsAsBytesfalseMailReceiverDataHandlerbyte[]contentTypeapplication/octet-streamSpring中文文档

如果未提供标头映射器,则消息有效负载由 提供。 该框架提供了一个,您可以使用一种策略将邮件内容转换为:MimeMessagejakarta.mailMailToStringTransformerStringSpring中文文档

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

从版本 4.3 开始,转换器处理嵌入式实例(以及之前处理的实例)。 transformer 是其子类,用于映射前面列表中的地址和主题标头。 如果要对消息执行一些其他转换,请考虑 子类化 。PartMultipartAbstractMailTransformerAbstractMailTransformerSpring中文文档

从版本 5.4 开始,当 no 提供时,is 和 is ,在生成的 Spring 消息的有效负载中按原样返回。 这样,在引用时,在流的后期按需加载 的内容。 上述所有转换仍然有效。headerMapperautoCloseFolderfalsesimpleContentfalseMimeMessageMimeMessageSpring中文文档

邮件命名空间支持

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

<?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中文文档

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

或者,您可以提供主机、用户名和密码,如以下示例所示:Spring中文文档

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

从版本 5.1.3 开始,如果提供了 , ane,则可以省略。 但是,必须使用适当的 Java 邮件属性配置 and,例如对于 SMTP:hostusernamemail-senderjava-mail-propertieshostusernameSpring中文文档

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

使用命名空间支持时,还可以使用消息转换器。 这样做可以简化前面提到的标头在发送到邮件出站通道适配器之前对任何邮件的应用。header-enricherSpring中文文档

以下示例假定有效负载是一个 Java Bean,具有指定属性的适当 getter,但您可以使用任何 SpEL 表达式:Spring中文文档

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

或者,您可以使用该属性来指定文本。 您还可以指定单个属性来控制现有标头的行为。valuedefault-overwriteoverwriteSpring中文文档

若要配置入站通道适配器,可以在轮询或事件驱动之间进行选择(假设您的邮件服务器支持 IMAP,否则轮询是唯一的选项)。 轮询通道适配器需要存储 URI 和要向其发送入站消息的通道。 URI 可以以 或 开头。 以下示例使用 URI:idlepop3imapimapSpring中文文档

<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 支持,则可能需要改为配置该元素。 由于该命令启用事件驱动的通知,因此此适配器不需要轮询器。 一旦收到新邮件可用,它就会向指定的通道发送邮件。 以下示例配置 IMAP 邮件通道:idleimap-idle-channel-adapteridleidleSpring中文文档

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

您可以通过创建和填充常规对象来提供,例如,使用 Spring 提供的命名空间。javaMailPropertiesjava.utils.PropertiesutilSpring中文文档

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

以下示例演示如何配置对象:java.util.PropertiesSpring中文文档

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

默认情况下,搜索基于默认值的邮件,即满足以下条件的所有邮件:ImapMailReceiverSearchTermSpring中文文档

自定义用户标志是 ,但您可以对其进行配置。 从 2.2 版开始,使用的 完全可以配置为 ,您可以使用属性注入它。 A 是具有单个方法的策略接口,可用于创建 使用的实例。 以下列表显示了该接口:spring-integration-mail-adapterSearchTermImapMailReceiverSearchTermStrategysearch-term-strategySearchTermStrategySearchTermImapMailReceiverSearchTermStrategySpring中文文档

public interface SearchTermStrategy {

    SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);

}

以下示例依赖于而不是默认值:TestSearchTermStrategySearchTermStrategySpring中文文档

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

有关邮件标记的信息,请参阅在最近不支持时标记 IMAP 邮件Spring中文文档

重要:IMAP PEEK

从 V4.1.1 开始,IMAP 邮件接收方使用 or JavaMail 属性(如果指定)。 以前,接收方会忽略该属性并始终设置标志。 现在,如果将此属性显式设置为 ,则消息 ise 标记为 ,而不考虑 的设置。 如果未指定,则保留先前的行为(peek 为 )。mail.imap.peekmail.imaps.peekPEEKfalse\SeenshouldMarkMessagesReadtrueSpring中文文档

IMAP 和连接丢失idle

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

在Gmail中观察到以下行为,但应该为您提供一些有关如何解决与其他提供商重新连接问题的提示。 但是,我们始终欢迎您提供反馈。 同样,以下注释基于 Gmail。

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

在这两种配置中,都是必需的属性。 您应该了解为什么需要。 问题出在 POP3 协议上,该协议对读取的消息一无所知。 它只能知道在单个会话中读取了什么。 这意味着,当您的 POP3 邮件适配器运行时,电子邮件在每次轮询期间可用时都会成功使用,并且不会多次传递任何一封电子邮件。 但是,一旦重新启动适配器并开始新会话,就会再次检索可能在上一个会话中检索到的所有电子邮件。 这就是POP3的本质。 有些人可能会争辩说,这应该是默认的。 换句话说,有两种有效且相互排斥的用法,因此很难选择一个最佳默认值。 您可能希望将适配器配置为唯一的电子邮件接收者,在这种情况下,您希望能够重新启动适配器,而不必担心以前传递的邮件不会再次传递。 在这种情况下,设置为最有意义。 但是,您可能有另一个用例,您可能希望让多个适配器监视电子邮件服务器及其内容。 换句话说,你想“偷看但不触摸”。 那么设置为更合适。 因此,由于很难为属性选择正确的默认值,因此我们将其设置为必需的属性。 把它留给你也意味着你不太可能最终做出意外的行为。channelshould-delete-messagesshould-delete-messagesshould-delete-messagestrueshould-delete-messagestrueshould-delete-messagesfalseshould-delete-messages
配置轮询电子邮件适配器的属性时,应了解要配置的用于检索邮件的协议。 例如,POP3 不支持此标志,这意味着将其设置为任一值都不起作用,因为邮件未标记为已读。should-mark-messages-as-read

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

您应该了解,这些操作(标记消息、已读消息和删除消息)是在收到消息之后但在处理消息之前执行的。 这可能会导致消息丢失。Spring中文文档

您可能希望考虑改用事务同步。 请参阅事务同步Spring中文文档

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

从 3.0 版本开始,IMAP 适配器会在发生异常时发出应用程序事件(特别是实例)。 这允许应用程序检测这些异常并对其采取措施。 您可以使用配置为接收其超类之一的 or any 来获取事件。idleImapIdleExceptionEvent<int-event:inbound-channel-adapter>ApplicationListenerImapIdleExceptionEvent
与任何出站通道适配器一样,如果引用的通道是 ,则应提供一个元素(请参阅终结点命名空间支持)。PollableChannel<poller>
如果您的用户名包含“@”字符,请使用“%40”而不是“@”,以避免解析来自底层 JavaMail API 的错误。
重要:IMAP PEEK

从 V4.1.1 开始,IMAP 邮件接收方使用 or JavaMail 属性(如果指定)。 以前,接收方会忽略该属性并始终设置标志。 现在,如果将此属性显式设置为 ,则消息 ise 标记为 ,而不考虑 的设置。 如果未指定,则保留先前的行为(peek 为 )。mail.imap.peekmail.imaps.peekPEEKfalse\SeenshouldMarkMessagesReadtrueSpring中文文档

在Gmail中观察到以下行为,但应该为您提供一些有关如何解决与其他提供商重新连接问题的提示。 但是,我们始终欢迎您提供反馈。 同样,以下注释基于 Gmail。
在这两种配置中,都是必需的属性。 您应该了解为什么需要。 问题出在 POP3 协议上,该协议对读取的消息一无所知。 它只能知道在单个会话中读取了什么。 这意味着,当您的 POP3 邮件适配器运行时,电子邮件在每次轮询期间可用时都会成功使用,并且不会多次传递任何一封电子邮件。 但是,一旦重新启动适配器并开始新会话,就会再次检索可能在上一个会话中检索到的所有电子邮件。 这就是POP3的本质。 有些人可能会争辩说,这应该是默认的。 换句话说,有两种有效且相互排斥的用法,因此很难选择一个最佳默认值。 您可能希望将适配器配置为唯一的电子邮件接收者,在这种情况下,您希望能够重新启动适配器,而不必担心以前传递的邮件不会再次传递。 在这种情况下,设置为最有意义。 但是,您可能有另一个用例,您可能希望让多个适配器监视电子邮件服务器及其内容。 换句话说,你想“偷看但不触摸”。 那么设置为更合适。 因此,由于很难为属性选择正确的默认值,因此我们将其设置为必需的属性。 把它留给你也意味着你不太可能最终做出意外的行为。channelshould-delete-messagesshould-delete-messagesshould-delete-messagestrueshould-delete-messagestrueshould-delete-messagesfalseshould-delete-messages
配置轮询电子邮件适配器的属性时,应了解要配置的用于检索邮件的协议。 例如,POP3 不支持此标志,这意味着将其设置为任一值都不起作用,因为邮件未标记为已读。should-mark-messages-as-read

您应该了解,这些操作(标记消息、已读消息和删除消息)是在收到消息之后但在处理消息之前执行的。 这可能会导致消息丢失。Spring中文文档

您可能希望考虑改用事务同步。 请参阅事务同步Spring中文文档

从 3.0 版本开始,IMAP 适配器会在发生异常时发出应用程序事件(特别是实例)。 这允许应用程序检测这些异常并对其采取措施。 您可以使用配置为接收其超类之一的 or any 来获取事件。idleImapIdleExceptionEvent<int-event:inbound-channel-adapter>ApplicationListenerImapIdleExceptionEvent

不支持在以下情况下标记 IMAP 邮件\Recent

如果为 true,则 IMAP 适配器设置标志。shouldMarkMessagesAsRead\SeenSpring中文文档

此外,当电子邮件服务器不支持该标志时,只要服务器支持用户标志,IMAP 适配器就会使用用户标志(默认情况下为 )标记邮件。 如果不是,则设置为 。 无论设置如何,都会应用这些标志。\Recentspring-integration-mail-adapterFlag.FLAGGEDtrueshouldMarkMessagesReadSpring中文文档

null 中所述,默认值将忽略如此标记的消息。SearchTermStrategySpring中文文档

从版本 4.2.2 开始,可以使用 on 来设置用户标志的名称。 这样做允许多个接收者使用不同的标志(只要邮件服务器支持用户标志)。 使用命名空间配置适配器时,该属性可用。setUserFlagMailReceiveruser-flagSpring中文文档

电子邮件过滤

很多时候,您可能会遇到过滤传入邮件的要求(例如,您只想阅读行中包含“Spring Integration”的电子邮件)。 可以通过将入站邮件适配器与基于表达式的 . 虽然它会起作用,但这种方法有一个缺点。 由于邮件在通过入站邮件适配器后将被过滤,因此所有此类邮件都将标记为已读 () 或未读(取决于属性的值)。 但是,实际上,仅当邮件通过筛选条件时,将邮件标记为邮件更有用。 这类似于在滚动浏览预览窗格中的所有邮件时查看电子邮件客户端,但仅标记实际打开并读取为 的邮件。SubjectFilterSEENshould-mark-messages-as-readSEENSEENSpring中文文档

Spring Integration 2.0.4 引入了 on 和 的属性。 此属性允许您提供 SpEL 和正则表达式的组合表达式。 例如,如果您只想阅读主题行中包含“Spring Integration”的电子邮件,则可以按如下方式配置属性:.mail-filter-expressioninbound-channel-adapterimap-idle-channel-adaptermail-filter-expressionmail-filter-expression="subject matches '(?i).Spring Integration."Spring中文文档

由于 是 SpEL 评估上下文的根上下文,因此可以筛选通过 提供的任何值,包括消息的实际正文。 这一点尤为重要,因为读取邮件正文通常会导致此类邮件被标记为默认。 但是,由于我们现在将每条传入消息的标志设置为“true”,因此只有显式标记为的消息才会标记为已读。jakarta.mail.internet.MimeMessageMimeMessageSEENPEEKSEENSpring中文文档

因此,在以下示例中,此适配器仅输出与筛选器表达式匹配的消息,并且仅将这些消息标记为已读:Spring中文文档

<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.*'"/>

在前面的示例中,由于该属性,此适配器仅生成主题行中包含“Spring Integration”的消息。mail-filter-expressionSpring中文文档

另一个合理的问题是,在下一次轮询或空闲事件中会发生什么,或者重新启动此类适配器时会发生什么。 可以过滤重复的按摩吗?换句话说,如果在最后一次检索时,您有五条新消息,但只有一条通过了过滤器,那么其他四条会发生什么? 他们会在下一次轮询时再次执行过滤逻辑还是闲置? 毕竟,它们没有被标记为. 答案是否定的。 由于电子邮件服务器设置并由Spring Integration邮件搜索过滤器使用的另一个标志(),它们不会受到重复处理。 文件夹实现设置此标志,以指示此邮件是此文件夹的新邮件。 也就是说,自上次打开此文件夹以来,它已到达。 换句话说,虽然我们的适配器可能会偷看电子邮件,但它也让电子邮件服务器知道此类电子邮件已被触摸,因此应标记为电子邮件服务器。SEENRECENTRECENTSpring中文文档

事务同步

通过入站适配器的事务同步,可以在事务提交或回滚后执行不同的操作。 您可以通过向轮询器或 . 即使不涉及“真实”事务,您仍然可以通过使用 with the element 来启用此功能。 有关详细信息,请参阅事务同步<transactional/><inbound-adapter/><imap-idle-inbound-adapter/>PseudoTransactionManager<transactional/>Spring中文文档

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

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

下面的示例显示了该类可能是什么样子:MoverSpring中文文档

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”。
要使消息在事务后仍可供操作,必须将 should-delete-messages 设置为“false”。

使用 Java DSL 配置通道适配器

为了在 Java DSL 中配置邮件组件,该框架提供了一个工厂,可以这样使用:o.s.i.mail.dsl.MailSpring中文文档

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