郵件支援

本節介紹如何在 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`,或者只是返回,從而讓您可以繼續進行自動重連。目前,自動重連會無限期執行,每十秒嘗試一次重連。

在兩種配置中,channelshould-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-adapterimap-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();
    }
}