郵件支援

本節說明如何在 Spring Integration 中使用郵件訊息。

您需要將此依賴項包含到您的專案中

  • Maven

  • Gradle

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

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 支援一些簡單的訊息映射策略。例如,如果訊息酬載是位元組陣列,則會映射到附件。對於簡單的基於文字的電子郵件,您可以提供基於字串的訊息酬載。在這種情況下,將建立一個 MailMessage,並將該 String 作為文字內容。如果您使用的訊息酬載類型其 toString() 方法傳回適當的郵件文字內容,請考慮在輸出郵件配接器之前新增 Spring Integration 的 ObjectToStringTransformer(有關更多詳細資訊,請參閱 使用 XML 設定轉換器 中的範例)。

您還可以從 MessageHeaders 設定輸出的 MailMessage 的某些值。如果可用,這些值會映射到輸出郵件的屬性,例如收件者(To、Cc 和 BCc)、fromreply-tosubject。標頭名稱由以下常數定義

 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 介面的已設定實例。有兩個實作:Pop3MailReceiverImapMailReceiver。實例化這兩者的最簡單方法是繞過郵件儲存區的 'uri' 到接收器的建構子,如下例所示

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

接收郵件的另一個選項是 IMAP idle 命令(如果您的郵件伺服器支援)。Spring Integration 提供 ImapIdleChannelAdapter,它本身就是一個產生訊息的端點。它委派給 ImapMailReceiver 的實例。下一節提供了使用 Spring Integration 命名空間支援在 'mail' 綱要中設定兩種輸入通道配接器的範例。

通常,當呼叫 IMAPMessage.getContent() 方法時,會呈現某些標頭以及主體(對於簡單的文字電子郵件),如下例所示

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

something

使用簡單的 MimeMessagegetContent() 會傳回郵件主體(前面範例中的 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,而無需任何轉換或急切內容載入。此功能透過以下選項組合啟用:未提供 headerMappersimpleContent 屬性為 false,且 autoCloseFolder 屬性為 falseMimeMessage 以產生的 Spring 訊息的酬載形式呈現。在這種情況下,唯一填入的標頭是上述 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE,用於必須在完成 MimeMessage 的處理後關閉的資料夾。

從 5.5.11 版開始,如果在 AbstractMailReceiver.receive() 之後未收到訊息或所有訊息都被篩選掉,則無論 autoCloseFolder 旗標如何,資料夾都會自動關閉。在這種情況下,對於圍繞 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE 標頭的可能邏輯,沒有任何下游產品。

從 6.0.5 版開始,ImapIdleChannelAdapter 不再執行非同步訊息發佈。這對於封鎖閒置接聽器迴圈以進行下游訊息處理(例如,具有大型附件)是必要的,因為郵件資料夾必須保持開啟。如果需要非同步交接,則可以使用 ExecutorChannel 作為此通道配接器的輸出通道。

輸入郵件訊息映射

預設情況下,輸入配接器產生的訊息的酬載是原始 MimeMessage。您可以使用該物件來查詢標頭和內容。從 4.3 版開始,您可以提供 HeaderMapper<MimeMessage> 來將標頭映射到 MessageHeaders。為了方便起見,Spring Integration 提供了 DefaultMailHeaderMapper 來實現此目的。它映射以下標頭

  • mail_fromfrom 位址的 String 表示形式。

  • mail_bcc:包含 bcc 位址的 String 陣列。

  • mail_cc:包含 cc 位址的 String 陣列。

  • mail_to:包含 to 位址的 String 陣列。

  • mail_replyToreplyTo 位址的 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 的範例包括 MessageMultipart。在這種情況下,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 版開始,當未提供 headerMapperautoCloseFolderfalsesimpleContentfalse 時,MimeMessage 會以產生的 Spring 訊息的酬載形式按原樣傳回。這樣,MimeMessage 的內容會在稍後的流程中被引用時按需載入。上述所有轉換仍然有效。

郵件命名空間支援

Spring Integration 為郵件相關設定提供命名空間。若要使用它,請設定以下綱要位置

<?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,則可以省略 hostusernamemail-sender。但是,hostusername 必須使用適當的 Java 郵件屬性進行設定,例如 SMTP

[email protected]
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587
與任何輸出通道配接器一樣,如果引用的通道是 PollableChannel,則應提供 <poller> 元素(請參閱 端點命名空間支援)。

當您使用命名空間支援時,您也可以使用 header-enricher 訊息轉換器。這樣做可以簡化將先前提及的標頭應用於任何訊息,然後再傳送到郵件輸出通道配接器。

以下範例假設酬載是一個 Java Bean,其中包含指定屬性的適當 getter,但您可以使用任何 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 可以 pop3imap 開頭。以下範例使用 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(如果支援)

  • 不是 ANSWERED

  • 不是 DELETED

  • 不是 SEEN

  • h尚未由此郵件接收器處理(透過使用自訂 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 版開始,如果指定了 JavaMail 屬性 mail.imap.peekmail.imaps.peek,則 IMAP 郵件接收器會使用它們。先前,接收器忽略了該屬性,並始終設定 PEEK 旗標。現在,如果您明確將此屬性設定為 false,則無論 shouldMarkMessagesRead 的設定為何,該訊息都會標記為 \Seen。如果未指定,則保留先前的行為(peek 為 true)。

IMAP idle 和連線遺失

當使用 IMAP idle 通道配接器時,與伺服器的連線可能會遺失(例如,透過網路故障),並且由於 JavaMail 文件明確聲明實際的 IMAP API 是實驗性的,因此了解 API 中的差異以及在設定 IMAP idle 配接器時如何處理這些差異非常重要。目前,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() 方法返回 StoreClosedExceptionFolderClosedException,或僅僅返回,從而讓您可以繼續進行自動重新連線。目前,自動重新連線無限期地運行,每十秒嘗試重新連線一次。

在兩種配置中,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 設定為何,都會套用這些標誌。

null 中所討論的,預設的 SearchTermStrategy 會忽略標記為此標誌的訊息。

從 4.2.2 版本開始,您可以使用 MailReceiver 上的 setUserFlag 設定使用者標誌的名稱。這樣做可讓多個接收器使用不同的標誌(只要郵件伺服器支援使用者標誌)。當使用命名空間配置配接器時,user-flag 屬性可用。

電子郵件訊息篩選

通常,您可能會遇到篩選傳入訊息的需求(例如,您只想讀取主旨行中包含 '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 郵件搜尋篩選器使用的標誌,它們不會受到重複處理。Folder 實作設定此標誌以指示此訊息對於此資料夾是新的。也就是說,它是在上次打開此資料夾之後到達的。換句話說,雖然我們的配接器可能會查看電子郵件,但它也會讓電子郵件伺服器知道已觸及此類電子郵件,因此電子郵件伺服器應將其標記為 RECENT

交易同步

輸入配接器的交易同步可讓您在交易提交或回滾後採取不同的動作。您可以透過將 <transactional/> 元素新增至輪詢的 <inbound-adapter/><imap-idle-inbound-adapter/> 的輪詢器來啟用交易同步。即使沒有涉及「真實」交易,您仍然可以使用 PseudoTransactionManager<transactional/> 元素來啟用此功能。有關更多資訊,請參閱 交易同步

由於不同的郵件伺服器,特別是一些伺服器的限制,目前我們僅為這些交易同步提供策略。您可以將訊息發送到其他 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();
    }
}