メールサポート

このセクションでは、Spring Integration でメールメッセージを操作する方法について説明します。

この依存関係をプロジェクトに含める必要があります。

<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 を使用した Transformer の構成の例を参照してください)。

MessageHeaders からの特定の値を使用して、送信 MailMessage を構成することもできます。利用可能な場合、値は受信者 (To、Cc、BCc)、fromreply-tosubject などの送信メールのプロパティにマップされます。ヘッダー名は、次の定数によって定義されます。

 MailHeaders.SUBJECT
 MailHeaders.TO
 MailHeaders.CC
 MailHeaders.BCC
 MailHeaders.FROM
 MailHeaders.REPLY_TO
MailHeaders では、対応する MailMessage 値をオーバーライドすることもできます。例: MailMessage.to が '[ メール保護 ] (英語) ' に設定され、MailHeaders.TO メッセージヘッダーが提供される場合、それが優先され、MailMessage の対応する値をオーバーライドします。

メール受信チャネルアダプター

Spring Integration は、MailReceivingMessageSource を使用した受信メールのサポートも提供します。Spring Integration 自身の MailReceiver インターフェースの構成済みインスタンスに委譲します。Pop3MailReceiver と ImapMailReceiver の 2 つの実装があります。これらのいずれかをインスタンス化する最も簡単な方法は、次の例に示すように、メールストアの "uri" を受信者のコンストラクターにバイパスすることです。

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

メールを受信するためのもう 1 つのオプションは、IMAP idle コマンドです (メールサーバーでサポートされている場合)。Spring Integration は、それ自体がメッセージ生成エンドポイントである ImapIdleChannelAdapter を提供します。ImapMailReceiver のインスタンスに委譲します。次のセクションでは、「メール」スキーマでの 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();
}

フォルダーを開いたままにしておくと、添付ファイル付きのメールのマルチパートコンテンツの解析中にサーバーとの通信が必要な場合に役立ちます。shouldDeleteMessages が AbstractMailReceiver でそれぞれ構成されている場合、IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE ヘッダーの close() は AbstractMailReceiver に委譲して、expunge オプションでフォルダーを閉じます。

バージョン 5.4 以降では、変換やコンテンツの積極的な読み込みを行わずに、MimeMessage をそのまま返すことができるようになりました。この機能は、次のオプションの組み合わせで有効になります: headerMapper が指定されていない場合、simpleContent プロパティは falseautoCloseFolder プロパティは false です。MimeMessage は、生成される Spring メッセージのペイロードとして存在します。この場合、設定される唯一のヘッダーは、MimeMessage の処理が完了したときに閉じる必要があるフォルダーの上記の IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE です。

バージョン 5.5.11 以降、メッセージが受信されなかった場合、autoCloseFolder フラグとは関係なくすべてのメッセージがフィルターで除外された場合、AbstractMailReceiver.receive() の後にフォルダーは自動的に閉じられます。この場合、IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE ヘッダー周辺の可能なロジックのダウンストリームを生成するものはありません。

バージョン 6.0.5 以降、ImapIdleChannelAdapter は非同期メッセージ発行を実行しなくなりました。これは、メールフォルダーを開いたままにする必要があるため、ダウンストリームのメッセージ処理 (大きな接続ファイルなど) のアイドルリスナーループをブロックするために必要です。非同期ハンドオフが必要な場合は、ExecutorChannel をこのチャネルアダプターの出力チャネルとして使用できます。

受信メールメッセージマッピング

デフォルトでは、受信アダプターによって生成されるメッセージのペイロードは生の MimeMessage です。そのオブジェクトを使用して、ヘッダーとコンテンツを調べることができます。バージョン 4.3 から、HeaderMapper<MimeMessage> を提供してヘッダーを MessageHeaders にマップできます。便宜上、Spring Integration はこの目的のために DefaultMailHeaderMapper を提供します。次のヘッダーをマップします。

  • mail_fromfrom アドレスの String 表現。

  • mail_bccbcc アドレスを含む String 配列。

  • mail_cccc アドレスを含む String 配列。

  • mail_toto アドレスを含む 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 の例は、Message および Multipart です。この場合、contentType ヘッダーは application/octet-stream です。この動作を変更して Multipart オブジェクトペイロードを受信するには、MailReceiver で embeddedPartsAsBytes を false に設定します。DataHandler にとって未知のコンテンツ型の場合、コンテンツは application/octet-stream の contentType ヘッダーを持つ byte[] としてレンダリングされます。

ヘッダーマッパーを指定しない場合、メッセージペイロードは jakarta.mail によって提示される MimeMessage です。フレームワークは、メールの内容を String に変換する戦略を使用してメッセージを変換するために使用できる MailToStringTransformer を提供します。

  • 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 である場合、生成された Spring メッセージのペイロードで MimeMessage がそのまま返されます。このようにして、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 が提供されている場合、hostusername および mail-sender を省略できます。ただし、host および username は、適切な Java メールプロパティで構成する必要があります。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"/>

javaMailProperties を提供するには、通常の java.utils.Properties オブジェクトを作成および設定します。たとえば、Spring が提供する util 名前空間を使用します。

ユーザー名に @ 文字が含まれている場合は、基盤となる JavaMail API からの解析エラーを回避するために、@ ではなく %40 を使用します。

次の例は、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 に基づいてメッセージを検索します。これは、以下のすべてのメールメッセージです。

  • 最近 (サポートされている場合)

  • 回答されていません

  • 削除されません

  • 見えない

  • hHave はこのメール受信者によって処理されていません (カスタム 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);

}

次の例は、デフォルトの SearchTermStrategy ではなく TestSearchTermStrategy に依存しています。

<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 以降、IMAP メールレシーバーは、指定されている場合、mail.imap.peek または mail.imaps.peek JavaMail プロパティを使用します。以前は、レシーバーはプロパティを無視し、常に PEEK フラグを設定していました。このプロパティを明示的に false に設定すると、メッセージは shouldMarkMessagesRead の設定に関係なく \Seen としてマークされます。指定しない場合、以前の動作が保持されます(ピークは true です)。

IMAP idle と失われた接続

IMAP idle チャネルアダプターを使用する場合、サーバーへの接続が失われる可能性があります (たとえば、ネットワーク障害によって)。JavaMail のドキュメントでは、実際の IMAP API は実験的であると明示的に述べられているため、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() メソッドが StoreClosedException または FolderClosedException を返すか、単に戻ることを強制する条件が常に存在することが保証されるため、自動再接続を続行できます。現在、自動再接続は無限に実行され、10 秒ごとに再接続が試行されます。

どちらの構成でも、channel と should-delete-messages は必須の属性です。should-delete-messages が必要な理由を理解する必要があります。課題は POP3 プロトコルにあり、POP3 プロトコルは読み取られたメッセージを認識しません。単一セッション内で何が読み取られたかのみを知ることができます。これは、POP3 メールアダプターが実行されると、メールは各ポーリング中に利用可能になると正常に消費され、単一のメールメッセージが複数回配信されることはないことを意味します。ただし、アダプターを再起動して新しいセッションを開始するとすぐに、前のセッションで取得された可能性のあるすべてのメールメッセージが再度取得されます。それが POP3 の性質です。should-delete-messages をデフォルトで true にするべきだと主張する人もいるかもしれません。言い換えれば、有効な 2 つの使用箇所が相互に排他的であるため、最適なデフォルトを 1 つ選択することが非常に困難になります。アダプターを唯一のメール受信者として構成することもできます。その場合、以前に配信されたメッセージが再度配信されないことを心配せずにアダプターを再起動できるようにする必要があります。この場合、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' 属性も受け入れます。ダウンストリーム例外がスローされ、「エラーチャネル」が指定されている場合、失敗したメッセージと元の例外を含む MessagingException メッセージがこのチャネルに送信されます。そうでない場合、ダウンストリームチャネルが同期である場合、そのような例外はチャネルアダプターによって警告としてログに記録されます。

3.0 リリース以降、IMAP idle アダプターは、例外が発生するとアプリケーションイベント(具体的には ImapIdleExceptionEvent インスタンス)を発行します。これにより、アプリケーションはこれらの例外を検出して処理できます。ImapIdleExceptionEvent またはそのスーパークラスの 1 つを受信するように構成された <int-event:inbound-channel-adapter> または任意の ApplicationListener を使用して、イベントを取得できます。

\Recent がサポートされていない場合の IMAP メッセージのマーキング

shouldMarkMessagesAsRead が true の場合、IMAP アダプターは \Seen フラグを設定します。

さらに、メールサーバーが \Recent フラグをサポートしていない場合、サーバーがユーザーフラグをサポートしている限り、IMAP アダプターはメッセージをユーザーフラグ (既定では spring-integration-mail-adapter) でマークします。そうでない場合、Flag.FLAGGED は true に設定されます。これらのフラグは、shouldMarkMessagesRead の設定に関係なく適用されます。ただし、バージョン 6.4 以降では、\Flagged も無効にできます。AbstractMailReceiver は、\Flagged の設定をスキップする setFlaggedAsFallback(boolean flaggedAsFallback) オプションを公開します。シナリオによっては、\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-adapter および imap-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" を含むメッセージのみがこのアダプターによって生成されます。

別の妥当な質問は、次のポーリングまたはアイドルイベントで何が起こるか、そのようなアダプターが再起動されたときに何が起こるかです。フィルタリングするマッサージの重複はありますか? つまり、最後の取得時に、5 つの新しいメッセージがあり、1 つだけがフィルターを通過した場合、他の 4 つのメッセージはどうなるでしょうか? 次のポーリングまたはアイドルで再びフィルタリングロジックを使用しますか? 結局、それらは SEEN としてマークされていませんでした。答えはいいえだ。メールサーバーによって設定され、Spring Integration メール検索フィルターによって使用される別のフラグ(RECENT)により、重複処理の対象にはなりません。フォルダー実装はこのフラグを設定して、このメッセージがこのフォルダーにとって新しいものであることを示します。つまり、このフォルダーが最後に開かれてから届きました。言い換えれば、アダプターはメールを覗き込むかもしれませんが、メールサーバーはそのようなメールがタッチされたため、メールサーバーによって 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();
    }
}