本节介绍如何在 Spring 集成中处理邮件消息。spring-doc.cn

您需要将此依赖项包含在您的项目中:spring-doc.cn

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

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

邮件发送通道适配器

Spring 集成通过 . 它委托给 Spring 的已配置实例,如下例所示:MailSendingMessageHandlerJavaMailSenderspring-doc.cn

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

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

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

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

 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 集成还通过 . 它委托给 Spring 集成自己的接口的已配置实例。 有两种实现: 和 。 实例化其中任何一个的最简单方法是将邮件存储的 'uri' 绕过到接收方的构造函数,如下例所示:MailReceivingMessageSourceMailReceiverPop3MailReceiverImapMailReceiverspring-doc.cn

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

接收邮件的另一个选项是 IMAP 命令(如果您的邮件服务器支持)。 Spring 集成提供了 ,它本身就是一个消息生成端点。 它将 . 下一节提供了在“mail”模式中使用 Spring 集成的名称空间支持配置两种类型的入站通道适配器的示例。idleImapIdleChannelAdapterImapMailReceiverspring-doc.cn

通常,当调用该方法时,会呈现某些 Headers 和正文(对于简单的文本电子邮件),如下例所示:IMAPMessage.getContent()spring-doc.cn

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

something

使用简单的 ,返回邮件正文(在前面的示例中为 )。MimeMessagegetContent()somethingspring-doc.cn

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

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

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

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

在解析包含附件的电子邮件的多部分内容时需要与服务器通信的情况下,保持文件夹打开非常有用。 的 在标题上委派给 to close the folder with 选项(如果分别在 上配置)。close()IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEAbstractMailReceiverexpungeshouldDeleteMessagesAbstractMailReceiverspring-doc.cn

从版本 5.4 开始,现在可以返回原样,而无需任何转换或预先加载内容。 此功能通过以下选项组合启用:no provided、property is 和 property is 。 该 作为生成的 Spring 消息的有效负载存在。 在这种情况下,唯一填充的标题是上述文件夹的标题,该文件夹在处理完成时必须关闭。MimeMessageheaderMappersimpleContentfalseautoCloseFolderfalseMimeMessageIntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEMimeMessagespring-doc.cn

从版本 5.5.11 开始,如果未收到任何邮件或独立于标志过滤掉所有邮件,则文件夹将自动关闭。 在这种情况下,对于 headers 周围的可能 logic ,下游没有什么可生成的。AbstractMailReceiver.receive()autoCloseFolderIntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEspring-doc.cn

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

通常,当调用该方法时,会呈现某些 Headers 和正文(对于简单的文本电子邮件),如下例所示:IMAPMessage.getContent()spring-doc.cn

入站邮件映射

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

启用消息映射后,有效负载取决于邮件消息及其实现。 电子邮件内容通常由 中的呈现。DataHandlerMimeMessagespring-doc.cn

对于电子邮件,有效负载为 a,标头与 相同。text/*StringcontentTypemail_contentTypespring-doc.cn

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

如果不提供标头映射器,则消息有效负载由 表示。 该框架提供了一个,您可以使用策略将邮件内容转换为 :MimeMessagejakarta.mailMailToStringTransformerStringspring-doc.cn

   ...
   .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 处理 embedded instances (以及以前处理的 instances)。 transformer 是 Map 前面列表中的 address 和 subject 标头的子类。 如果您希望对消息执行其他转换,请考虑 subclassing 。PartMultipartAbstractMailTransformerAbstractMailTransformerspring-doc.cn

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

Mail Namespace 支持

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

<?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-doc.cn

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

或者,您可以提供 host、username 和 password,如下例所示:spring-doc.cn

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

从版本 5.1.3 开始,如果提供了 , ane,则可以省略。 但是,必须为和 配置适当的 Java 邮件属性,例如对于 SMTP:hostusernamemail-senderjava-mail-propertieshostusernamespring-doc.cn

[email protected]
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587
与任何出站 Channel Adapter 一样,如果引用的通道是 ,则应提供一个元素(请参阅 Endpoint Namespace Support)。PollableChannel<poller>

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

以下示例假定有效负载是一个 Java Bean,具有指定属性的适当 getter,但您可以使用任何 SPEL 表达式:spring-doc.cn

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

或者,您可以使用 attribute 指定文本。 您还可以指定 和 individual 属性来控制现有标头的行为。valuedefault-overwriteoverwritespring-doc.cn

要配置入站通道适配器,您可以选择轮询或事件驱动(假设您的邮件服务器支持 IMAP — 如果不支持,则轮询是唯一的选项)。 轮询通道适配器需要存储 URI 和将入站消息发送到的通道。 URI 可以以 或 开头。 以下示例使用 URI:idlepop3imapimapspring-doc.cn

<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,则可能需要改为配置该元素。 由于该命令启用了事件驱动的通知,因此此适配器不需要 Poller。 一旦收到有新邮件可用的通知,它就会向指定的通道发送消息。 以下示例配置 IMAP 邮件通道:idleimap-idle-channel-adapteridleidlespring-doc.cn

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

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

以下示例说明如何配置对象:java.util.Propertiesspring-doc.cn

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

默认情况下,根据 default 搜索邮件,默认邮件是满足以下条件的所有邮件邮件:ImapMailReceiverSearchTermspring-doc.cn

自定义用户标志为 ,但您可以对其进行配置。 从版本 2.2 开始,the used by the 完全可以通过 进行配置,您可以使用 attribute 注入。 A 是一个策略接口,具有单个方法,允许您创建 used 的实例。 下面的清单显示了该接口:spring-integration-mail-adapterSearchTermImapMailReceiverSearchTermStrategysearch-term-strategySearchTermStrategySearchTermImapMailReceiverSearchTermStrategyspring-doc.cn

public interface SearchTermStrategy {

    SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);

}

以下示例依赖于而不是 default :TestSearchTermStrategySearchTermStrategyspring-doc.cn

<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 邮件spring-doc.cn

重要:IMAP PEEK

从版本 4.1.1 开始,IMAP 邮件接收器使用 or JavaMail 属性(如果指定)。 以前,接收方会忽略该属性并始终设置 flag。 现在,如果将此属性显式设置为 ,则无论 的设置如何,消息 ise 都会标记为 。 如果未指定,则保留以前的行为(peek 为 )。mail.imap.peekmail.imaps.peekPEEKfalse\SeenshouldMarkMessagesReadtruespring-doc.cn

IMAP 和丢失的连接idle

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

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

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

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

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

您应该了解,这些操作(将消息标记为已读和删除消息)是在收到消息之后但在处理消息之前执行的。 这可能会导致消息丢失。spring-doc.cn

您可能希望考虑改用事务同步。 请参阅 事务同步spring-doc.cn

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

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

从版本 4.1.1 开始,IMAP 邮件接收器使用 or JavaMail 属性(如果指定)。 以前,接收方会忽略该属性并始终设置 flag。 现在,如果将此属性显式设置为 ,则无论 的设置如何,消息 ise 都会标记为 。 如果未指定,则保留以前的行为(peek 为 )。mail.imap.peekmail.imaps.peekPEEKfalse\SeenshouldMarkMessagesReadtruespring-doc.cn

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

您应该了解,这些操作(将消息标记为已读和删除消息)是在收到消息之后但在处理消息之前执行的。 这可能会导致消息丢失。spring-doc.cn

您可能希望考虑改用事务同步。 请参阅 事务同步spring-doc.cn

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

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

如果为 true,则 IMAP 适配器将设置该标志。shouldMarkMessagesAsRead\Seenspring-doc.cn

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

null 中所述,默认会忽略如此标记的消息。SearchTermStrategyspring-doc.cn

从版本 4.2.2 开始,您可以使用 . 这样做可以让多个接收者使用不同的标志(只要邮件服务器支持用户标志)。 使用命名空间配置适配器时,该属性可用。setUserFlagMailReceiveruser-flagspring-doc.cn

电子邮件筛选

很多时候,你可能会遇到过滤传入消息的要求(例如,你只想阅读行中包含 'Spring Integration' 的电子邮件)。 您可以通过将入站邮件适配器与 expression-based 连接来实现此目的。 尽管它会奏效,但这种方法也有缺点。 由于邮件在通过入站邮件适配器后将被过滤,因此所有此类邮件都将被标记为已读 () 或未读(取决于 attribute 的值)。 但是,在现实中,将消息标记为仅在通过过滤条件时才有用。 这类似于在预览窗格中滚动浏览所有邮件时查看电子邮件客户端,但仅将实际打开并读取为 的邮件标记为 。SubjectFilterSEENshould-mark-messages-as-readSEENSEENspring-doc.cn

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

由于 是 SPEL 评估上下文的根上下文,因此您可以通过 过滤任何可用的值,包括消息的实际正文。 这一点尤其重要,因为读取消息的正文通常会导致此类消息被标记为默认。 但是,由于我们现在将每条传入消息的标志设置为 'true',因此只有明确标记为 的消息才会被标记为已读。jakarta.mail.internet.MimeMessageMimeMessageSEENPEEKSEENspring-doc.cn

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

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

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

事务同步

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

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

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

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 中配置 mail 组件,框架提供了一个工厂,可以像这样使用:o.s.i.mail.dsl.Mailspring-doc.cn

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