邮件支持

邮件支持

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

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

Maven 系列
<dependency>
    <groupId>org.springframework.integration</groupId>
    <artifactId>spring-integration-mail</artifactId>
    <version>6.1.9</version>
</dependency>
Gradle
compile "org.springframework.integration:spring-integration-mail:6.1.9"

jakarta.mail:jakarta.mail-api必须通过特定于供应商的实施来包含。spring-doc.cadn.net.cn

邮件发送通道适配器

Spring 集成通过MailSendingMessageHandler. 它委托给 Spring 的JavaMailSender,如下例所示:spring-doc.cadn.net.cn

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

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

MailSendingMessageHandler具有各种使用 Spring 的MailMessage抽象化。 如果收到的消息的有效负载已经是MailMessage实例,则直接发送。 因此,我们通常建议您在此 consumer 前面加上一个 transformer for importantMailMessage施工要求。 但是, Spring 集成支持一些简单的消息 Map 策略。 例如,如果消息负载是映射到附件的字节数组。 对于简单的基于文本的电子邮件,您可以提供基于字符串的消息负载。 在这种情况下,一个MailMessage是使用String作为文本内容。 如果您使用的消息负载类型,其toString()方法返回适当的邮件文本内容,请考虑添加 Spring 集成的ObjectToStringTransformer在出站邮件适配器之前(有关更多详细信息,请参阅使用 XML 配置转换器中的示例)。spring-doc.cadn.net.cn

您还可以配置出站MailMessage具有MessageHeaders. 如果可用,值将映射到出站邮件的属性,例如收件人(To、Cc 和 BCc)、fromreply-tosubject. 标头名称由以下常量定义:spring-doc.cadn.net.cn

 MailHeaders.SUBJECT
 MailHeaders.TO
 MailHeaders.CC
 MailHeaders.BCC
 MailHeaders.FROM
 MailHeaders.REPLY_TO
MailHeaders还允许您覆盖相应的MailMessage值。 例如,如果MailMessage.to设置为 '[email protected]' 并且MailHeaders.TOmessage 标头,则它优先并覆盖MailMessage.

邮件接收通道适配器

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

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

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

通常,当IMAPMessage.getContent()方法,则呈现某些标头和正文(对于简单的文本电子邮件),如下例所示:spring-doc.cadn.net.cn

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

something

使用简单的MimeMessage,getContent()返回邮件正文 (something在前面的示例中)。spring-doc.cadn.net.cn

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

从版本 5.0 开始,源自 IMAP 源的邮件根据IMAPMessage.getContent()行为,无论是否提供标头映射器。 如果您不使用标头映射器,并且希望恢复到以前仅渲染主体的行为,请将simpleContentboolean 属性设置为true. 现在,无论是否使用标头映射器,此属性都控制渲染。 现在,当提供标头映射器时,它允许仅正文渲染。spring-doc.cadn.net.cn

从版本 5.2 开始,autoCloseFolder选项。 将其设置为false不会在提取后自动关闭文件夹,而是使用IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE标头(请参阅MessageHeaderAccessor应用程序接口有关更多信息)将填充到从通道适配器发送到 Producer 的每条消息中。 这不适用于Pop3MailReceiver因为它依赖于打开和关闭文件夹来获取新消息。 目标应用程序负责调用close()在此标头上,只要下游流中需要:spring-doc.cadn.net.cn

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

在解析包含附件的电子邮件的多部分内容时需要与服务器通信的情况下,保持文件夹打开非常有用。 这close()IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCEheader 委托给AbstractMailReceiver以关闭文件夹expungeoption (如果shouldDeleteMessages分别在AbstractMailReceiver.spring-doc.cadn.net.cn

从版本 5.4 开始,现在可以返回MimeMessage原样,没有任何转换或 Eager 内容加载。 此功能通过以下选项组合启用:否headerMapper,则simpleContentproperty 为falseautoCloseFolderproperty 为false. 这MimeMessage作为生成的 Spring 消息的有效负载存在。 在这种情况下,唯一填充的标头是上面提到的IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE对于在处理MimeMessage已完成。spring-doc.cadn.net.cn

从版本 5.5.11 开始,文件夹将在AbstractMailReceiver.receive()如果未收到任何消息或所有消息都被独立于autoCloseFolder旗。 在这种情况下,对于可能的 logic around 周围没有可生成的任何下游IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE页眉。spring-doc.cadn.net.cn

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

入站邮件映射

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

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

对于text/*email 中,有效负载是一个StringcontentTypeheader 与mail_contentType.spring-doc.cadn.net.cn

对于带有嵌入式jakarta.mail.Part实例、DataHandler通常会渲染一个Part对象。 这些对象不是Serializable并且不适合使用替代技术进行电子监管,例如Kryo. 因此,默认情况下,启用映射时,此类有效负载将呈现为 rawbyte[]包含Part数据。 示例PartMessageMultipart. 这contentTypeheader 为application/octet-stream在这种情况下。 要更改此行为并接收MultipartObject payload 中,将embeddedPartsAsBytesfalseMailReceiver. 对于DataHandler,内容将呈现为byte[]替换为contentType的标头application/octet-stream.spring-doc.cadn.net.cn

当您不提供标头映射器时,消息负载是MimeMessage主办单位jakarta.mail. 该框架提供了一个MailToStringTransformer,您可以使用策略将邮件内容转换为String:spring-doc.cadn.net.cn

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

从版本 4.3 开始,transformer 将 embeddedPart实例(以及Multipart实例,这些实例之前已处理过)。 transformer 是AbstractMailTransformer这将映射前面列表中的 address 和 subject 标头。 如果您希望对消息执行其他转换,请考虑子类化AbstractMailTransformer.spring-doc.cadn.net.cn

从版本 5.4 开始,当没有headerMapper提供,autoCloseFolderfalsesimpleContentfalseMimeMessage在生成的 Spring 消息的有效负载中按原样返回。 这样,MimeMessage在流的后面被引用时按需加载。 上述所有转换仍然有效。spring-doc.cadn.net.cn

Mail 命名空间支持

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

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

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

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

从版本 5.1.3 开始,host,usernamemail-sender可以省略,如果java-mail-properties。 但是,hostusername必须配置适当的 Java 邮件属性,例如对于 SMTP:spring-doc.cadn.net.cn

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

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

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

或者,您可以使用value属性指定文本。 您还可以指定default-overwrite和个人overwrite属性来控制现有标头的行为。spring-doc.cadn.net.cn

要配置入站通道适配器,您可以选择轮询或事件驱动(假设您的邮件服务器支持 IMAPidle— 如果不是,则轮询是唯一的选项)。 轮询通道适配器需要存储 URI 和将入站消息发送到的通道。 URI 可以以pop3imap. 以下示例使用imapURI 中:spring-doc.cadn.net.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>

如果您确实有 IMAPidlesupport 支持,您可能需要配置imap-idle-channel-adapter元素。 由于idle命令启用事件驱动的通知,此适配器不需要 Poller 。 一旦收到有新邮件可用的通知,它就会向指定的通道发送消息。 以下示例配置 IMAPidle邮件频道:spring-doc.cadn.net.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"/>

您可以提供javaMailProperties通过创建并填充常规的java.utils.Propertiesobject — 例如,通过使用util命名空间。spring-doc.cadn.net.cn

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

以下示例显示如何配置java.util.Properties对象:spring-doc.cadn.net.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>

默认情况下,ImapMailReceiver根据默认SearchTerm,即满足以下条件的所有邮件消息:spring-doc.cadn.net.cn

自定义用户标志为spring-integration-mail-adapter,但您可以对其进行配置。 从 2.2 版本开始,SearchTermImapMailReceiver可完全配置SearchTermStrategy,您可以使用search-term-strategy属性。 一个SearchTermStrategy是一个策略接口,其中包含一个方法,允许您创建SearchTermImapMailReceiver. 下面的清单显示了SearchTermStrategy接口:spring-doc.cadn.net.cn

public interface SearchTermStrategy {

    SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);

}

以下示例依赖于TestSearchTermStrategy而不是默认的SearchTermStrategy:spring-doc.cadn.net.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"/>
重要:IMAP PEEK

从版本 4.1.1 开始,IMAP 邮件接收器使用mail.imap.peekmail.imaps.peekJavaMail 属性(如果指定)。 以前,接收器会忽略该属性,并始终将PEEK旗。 现在,如果您将此属性显式设置为false,则消息 ISE 标记为\Seen无论shouldMarkMessagesRead. 如果未指定,则保留之前的行为(peek 为true).spring-doc.cadn.net.cn

IMAP 协议idle和 Lost Connections

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

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

在 JavaMail 1.4.1 中,如果将mail.imaps.timeout属性设置为相对较短的时间(在我们的测试中约为 5 分钟),IMAPFolder.idle()抛出FolderClosedException在此超时之后。 但是,如果未设置此属性(它应该是无限期的),则IMAPFolder.idle()method 永远不会返回,也永远不会引发异常。 但是,如果连接在短时间内丢失(在我们的测试中不到 10 分钟),它会自动重新连接。 但是,如果连接长时间丢失(超过 10 分钟),IMAPFolder.idle(),不抛出FolderClosedException并且不会重新建立连接,而是无限期地保持 blocked 状态,因此如果不重新启动适配器,则无法重新连接。 因此,使 JavaMail 1.4.1 的重新连接工作的唯一方法是将mail.imaps.timeoutproperty 显式地附加到某个值,但这也意味着这样的值应该相对较短(小于 10 分钟),并且应该相对较快地重新建立连接。 同样,它与 Gmail 以外的提供商可能有所不同。 在 JavaMail 中,1.4.3 对 API 进行了重大改进,确保始终存在强制IMAPFolder.idle()method 返回StoreClosedExceptionFolderClosedException或者直接返回,从而让您继续进行自动重新连接。 目前,自动重新连接无限运行,每 10 秒尝试一次重新连接。spring-doc.cadn.net.cn

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

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

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

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

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

从 3.0 版本开始,IMAPidleadapter 发出应用程序事件(特别是ImapIdleExceptionEvent实例)。 这允许应用程序检测并处理这些异常。 您可以使用<int-event:inbound-channel-adapter>或任何ApplicationListener配置为接收ImapIdleExceptionEvent或它的超级类之一。

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

如果shouldMarkMessagesAsRead为 true,则 IMAP 适配器会将\Seen旗。spring-doc.cadn.net.cn

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

[search-term] 中所述,默认的SearchTermStrategy忽略已标记的消息。spring-doc.cadn.net.cn

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

电子邮件筛选

很多时候,您可能会遇到过滤传入消息的要求(例如,您只想阅读Subject行)。 您可以通过将入站邮件适配器与基于表达式的Filter. 尽管它会奏效,但这种方法也有缺点。 由于邮件在通过入站邮件适配器后会被过滤,因此所有此类邮件都将被标记为已读 (SEEN) 或 unreads(取决于should-mark-messages-as-read属性)。 但是,在现实中,将消息标记为SEEN仅当它们通过筛选条件时。 这类似于在预览窗格中滚动浏览所有邮件时查看电子邮件客户端,但仅将实际打开并读取为 的邮件进行标记SEEN.spring-doc.cadn.net.cn

Spring Integration 2.0.4 引入了mail-filter-expression属性inbound-channel-adapterimap-idle-channel-adapter. 此属性允许您提供作为 SPEL 和正则表达式组合的表达式。 例如,如果您只想读取主题行中包含 'Spring Integration' 的电子邮件,则可以配置mail-filter-expression属性,如下所示:mail-filter-expression="subject matches '(?i).Spring Integration.".spring-doc.cadn.net.cn

因为jakarta.mail.internet.MimeMessage是 SPEL 评估上下文的根上下文,您可以通过MimeMessage,包括消息的实际正文。 这一点特别重要,因为读取消息的正文通常会导致此类消息被标记为SEEN默认情况下。 但是,由于我们现在将PEEK标志设置为 'true',则只有明确标记为SEEN标记为已读。spring-doc.cadn.net.cn

因此,在以下示例中,此适配器仅输出与筛选表达式匹配的消息,并且仅将这些消息标记为已读:spring-doc.cadn.net.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.*'"/>

在前面的示例中,由于mail-filter-expression属性,则此适配器仅生成主题行中包含 'Spring Integration' 的消息。spring-doc.cadn.net.cn

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

事务同步

入站适配器的事务同步允许您在事务提交或回滚后执行不同的作。 您可以通过添加<transactional/>元素添加到轮询的 Poller 中<inbound-adapter/>或发送到<imap-idle-inbound-adapter/>. 即使不涉及“真实”交易,您仍然可以通过使用PseudoTransactionManager使用<transactional/>元素。 有关更多信息,请参阅事务同步spring-doc.cadn.net.cn

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

以下示例显示了Mover类可能如下所示:spring-doc.cadn.net.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'。

使用 Java DSL 配置通道适配器

为了在 Java DSL 中配置邮件组件,框架提供了一个o.s.i.mail.dsl.Mailfactory,可以像这样使用:spring-doc.cadn.net.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();
    }
}

APP信息