此版本仍在开发中,尚不被认为是稳定的。对于最新的稳定版本,请使用 Spring Integration 6.5.1spring-doc.cadn.net.cn

邮件支持

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

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

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

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

邮件发送通道适配器

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

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

 MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);

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

您还可以配置出站MailMessage某些值来自MessageHeaders. 如果可用,则值将映射到出站邮件的属性,例如收件人(收件人、抄送和 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 header 时,它优先并覆盖MailMessage.

邮件接收通道适配器

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

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

接收邮件的另一种选择是 IMAPidle命令(如果您的邮件服务器支持)。 Spring Integration 提供了ImapIdleChannelAdapter,它本身就是一个生成消息的端点。 它委托给ImapMailReceiver. 下一节提供了在“mail”模式中使用 Spring Integration 的命名空间支持配置两种类型的入站通道适配器的示例。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 版中引入的邮件映射增强功能进一步加剧了这种不一致,因为当提供标头映射器时,有效负载是由IMAPMessage.getContent()方法。 这意味着 IMAP 内容会有所不同,具体取决于是否提供了标头映射器。spring-doc.cadn.net.cn

从版本 5.0 开始,源自 IMAP 源的消息将按照IMAPMessage.getContent()行为,无论是否提供了标头映射器。 如果您不使用标头映射器,并且希望恢复到之前仅渲染正文的行为,请将simpleContent邮件接收器上的布尔属性设置为true. 现在,无论是否使用标头映射器,此属性都会控制渲染。 现在,当提供标头映射器时,它允许仅渲染正文。spring-doc.cadn.net.cn

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

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

在解析带有附件的电子邮件的多部分内容期间需要与服务器通信的情况下,保持文件夹打开非常有用。 这close()IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE标头委托给AbstractMailReceiver以关闭文件夹,并使用expunge选项shouldDeleteMessages分别在AbstractMailReceiver.spring-doc.cadn.net.cn

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

从 5.5.11 版本开始,文件夹在AbstractMailReceiver.receive()如果未收到任何消息或所有消息都被过滤掉,则与autoCloseFolder旗。 在这种情况下,没有任何东西可以为可能的逻辑生成下游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 Integration 提供了一个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. 因此,默认情况下,启用映射时,此类有效负载将呈现为原始负载byte[]包含Part数据。 示例PartMessageMultipart. 这contentTypeheader 是application/octet-stream在这种情况下。 要更改此行为并接收Multipart对象有效负载, 设置embeddedPartsAsBytesfalseMailReceiver. 对于未知的内容类型DataHandler,则内容将呈现为byte[]使用contentType的标题application/octet-stream.spring-doc.cadn.net.cn

如果不提供标头映射器,则消息有效负载是MimeMessage主办方jakarta.mail. 该框架提供了一个MailToStringTransformer您可以使用该策略将邮件内容转换为String:spring-doc.cadn.net.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 版开始,转换器处理嵌入Part实例(以及Multipart实例,之前已处理过)。 transformer 是AbstractMailTransformer映射前面列表中的地址和主题标头。 如果您希望对消息执行一些其他转换,请考虑子类化AbstractMailTransformer.spring-doc.cadn.net.cn

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

邮件命名空间支持

Spring Integration 为与邮件相关的配置提供了一个命名空间。 若要使用它,请配置以下架构位置: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"/>

或者,您可以提供主机、用户名和密码,如以下示例所示:spring-doc.cadn.net.cn

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

从 5.1.3 版本开始,host,username阿内mail-sender如果java-mail-properties被提供。 但是,hostusername必须配置适当的 Java 邮件属性,例如对于 SMTP:spring-doc.cadn.net.cn

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

使用命名空间支持时,还可以使用header-enricher消息转换器。 这样做可以简化在发送到邮件出站通道适配器之前将前面提到的标头应用于任何邮件。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命令启用事件驱动通知,此适配器不需要轮询器。一旦收到新邮件可用的通知,它就会立即向指定通道发送消息。以下示例配置 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.Properties对象 — 例如,通过使用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,则标记为\Seen无论设置如何shouldMarkMessagesRead. 如果未指定,则保留先前的行为(peek 是true).spring-doc.cadn.net.cn

IMAPidle和失去的连接

使用 IMAP 时idlechannel adapter,则与服务器的连接可能会丢失(例如,由于网络故障),并且由于 JavaMail 文档明确指出实际的 IMAP API 是实验性的,因此了解 API 中的差异以及如何在配置 IMAP 时处理它们非常重要idle适配器。 目前,Spring Integration 邮件适配器已使用 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()方法从不返回,也从不抛出异常。 但是,如果连接在短时间内丢失(在我们的测试中不到 10 分钟),它会自动重新连接。 但是,如果连接长时间(超过 10 分钟)丢失,IMAPFolder.idle(),不抛FolderClosedException并且不会重新建立连接,并且无限期地保持阻塞状态,因此如果不重新启动适配器,您就无法重新连接。 因此,使用 JavaMail 1.4.1 重新连接的唯一方法是将mail.imaps.timeout属性显式设置为某个值,但这也意味着该值应该相对较短(10 分钟以内),并且应该相对较快地重新建立连接。 同样,与 Gmail 以外的提供商可能会有所不同。 在 JavaMail 1.4.3 中,对 API 进行了重大改进,确保始终有一个条件强制IMAPFolder.idle()返回的方法StoreClosedExceptionFolderClosedException或者简单地返回,从而让您继续自动重新连接。 目前,自动重新连接无限运行,每十秒尝试一次重新连接。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 不支持此标志,这意味着将其设置为任一值都无效,因为消息未标记为已读。

在无提示断开连接的情况下,会定期在后台运行空闲取消任务(通常会立即处理新的空闲)。 要控制此间隔,请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' 属性。 如果抛出下游异常并指定了“错误通道”,则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. 无论shouldMarkMessagesRead设置。 但是,从版本 6.4 开始,\Flagged也可以禁用。 这AbstractMailReceiver公开一个setFlaggedAsFallback(boolean flaggedAsFallback)跳过设置的选项\Flagged. 在某些情况下,无论邮箱中的邮件上都不需要此类标志\Recent或者用户标志也没有得到支持。spring-doc.cadn.net.cn

SearchTerm,默认值SearchTermStrategy忽略被标记的邮件。spring-doc.cadn.net.cn

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

电子邮件过滤

很多时候,您可能会遇到过滤传入邮件的要求(例如,您只想读取Subject线)。 您可以通过将入站邮件适配器与基于表达式的Filter. 虽然它会奏效,但这种方法有一个缺点。 由于邮件将在通过入站邮件适配器后进行过滤,因此所有此类邮件都将标记为已读 (SEEN) 或未读(取决于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

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

事务同步

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

由于邮件服务器不同,特别是某些服务器的限制,目前我们只为这些事务同步提供策略。 您可以将消息发送到其他一些 Spring Integration 组件或调用自定义 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();
    }
}