Mail Support
本部分介绍了如何在 Spring Integration 中处理邮件信息。
This section describes how to work with mail messages in Spring Integration.
你需要将此依赖项包含在你的项目中:
You need to include this dependency into your project:
-
Maven
-
Gradle
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mail</artifactId>
<version>{project-version}</version>
</dependency>
compile "org.springframework.integration:spring-integration-mail:{project-version}"
jakarta.mail:jakarta.mail-api
必须通过特定于供应商的实现来包含。
The jakarta.mail:jakarta.mail-api
must be included via vendor-specific implementation.
Mail-sending Channel Adapter
Spring Integration 使用 MailSendingMessageHandler
为发件邮件提供支持。它委托给一个配置的 Spring 的 JavaMailSender
实例, 如下所示:
Spring Integration provides support for outbound email with the MailSendingMessageHandler
.
It delegates to a configured instance of Spring’s JavaMailSender
, as the following example shows:
JavaMailSender mailSender = context.getBean("mailSender", JavaMailSender.class);
MailSendingMessageHandler mailSendingHandler = new MailSendingMessageHandler(mailSender);
MailSendingMessageHandler
有多种映射策略,使用 Spring 的 MailMessage
抽象。如果接收到的消息的负载已经是 MailMessage
实例,则直接发送它。因此,我们通常建议在该消费者之前加上一个变压器,以满足非平凡的 MailMessage
构造要求。但是,Spring 集成支持一些简单的消息映射策略。例如,如果消息负载是字节数组,则将其映射到附件。对于基于文本的简单电子邮件,您可以提供基于字符串的消息负载。在这种情况下,将创建一个 MailMessage
并将该 String
作为文本内容。如果您使用的是 toString()
方法返回适当邮件文本内容的消息负载类型,请考虑在出站邮件适配器之前添加 Spring 集成的 ObjectToStringTransformer
(有关更多详细信息,请参见 Configuring a Transformer with XML 中的示例)。
MailSendingMessageHandler
has various mapping strategies that use Spring’s MailMessage
abstraction.
If the received message’s payload is already a MailMessage
instance, it is sent directly.
Therefore, we generally recommend that you precede this consumer with a transformer for non-trivial MailMessage
construction requirements.
However, Spring Integration supports a few simple message mapping strategies.
For example, if the message payload is a byte array, that is mapped to an attachment.
For simple text-based emails, you can provide a string-based message payload.
In that case, a MailMessage
is created with that String
as the text content.
If you work with a message payload type whose toString()
method returns appropriate mail text content, consider adding Spring Integration’s ObjectToStringTransformer
prior to the outbound mail adapter (see the example in Configuring a Transformer with XML for more detail).
您还可以使用 MessageHeaders
中的某些值配置发件 MailMessage
。如果有, 这些值会映射到发件邮件的属性, 例如收件人(To、Cc 和 BCc)、from
、reply-to
和 subject
。头名称由以下常量定义:
You can also configure the outbound MailMessage
with certain values from MessageHeaders
.
If available, values are mapped to the outbound mail’s properties, such as the recipients (To, Cc, and BCc), the from
, the reply-to
, and the subject
.
The header names are defined by the following constants:
MailHeaders.SUBJECT
MailHeaders.TO
MailHeaders.CC
MailHeaders.BCC
MailHeaders.FROM
MailHeaders.REPLY_TO
|
|
Mail-receiving Channel Adapter
Spring Integration 还使用 MailReceivingMessageSource
为收件邮件提供支持。它委托给 Spring Integration 自身的 MailReceiver
接口的一个配置实例。有两种实现: Pop3MailReceiver
和 ImapMailReceiver
。实例化其中的任何一种的最简单方法是将邮件存储的 “uri” 绕过接收者的构造函数, 如下所示:
Spring Integration also provides support for inbound email with the MailReceivingMessageSource
.
It delegates to a configured instance of Spring Integration’s own MailReceiver
interface.
There are two implementations: Pop3MailReceiver
and ImapMailReceiver
.
The easiest way to instantiate either of these is bypassing the 'uri' for a mail store to the receiver’s constructor, as the following example shows:
MailReceiver receiver = new Pop3MailReceiver("pop3://usr:pwd@localhost/INBOX");
另一种接收邮件的选项是 IMAP idle
命令(如果您的邮件服务器支持)。Spring Integration 提供 ImapIdleChannelAdapter
, 它本身就是一个消息生成端点。它委托给 ImapMailReceiver
的一个实例。下一部分提供使用 Spring Integration 在 “mail” 模式中使用命名空间支持配置这两种类型的入站通道适配器的示例。
Another option for receiving mail is the IMAP idle
command (if supported by your mail server).
Spring Integration provides the ImapIdleChannelAdapter
, which is itself a message-producing endpoint.
It delegates to an instance of the ImapMailReceiver
.
The next section has examples of configuring both types of inbound channel adapter with Spring Integration’s namespace support in the 'mail' schema.
通常, 当调用 IMAPMessage.getContent()
方法时, 某些头以及正文将被渲染(对于一个简单的文本电子邮件), 如下所示:
Normally, when the IMAPMessage.getContent()
method is called, certain headers as well as the body are rendered (for a simple text email), as the following example shows:
To: thing1@things.com
From: thing2@morethings.com
Subject: Test Email
something
使用一个简单的 MimeMessage
, getContent()
将返回邮件正文(在前一个示例中为 “something”)。
With a simple MimeMessage
, getContent()
returns the mail body (something
in the preceding example).
从 2.2 版开始,该框架会急切地获取 IMAP 消息并将它们作为 MimeMessage
的内部子类公开。这导致了改变 getContent()
行为的副作用。由于在 4.3 版中引入了 Mail Mapping 增强,因此这种不一致的情况进一步加剧,因为当提供标题映射器时,负载由 IMAPMessage.getContent()
方法呈现。这意味着 IMAP 内容有所不同,具体取决于是否提供了标题映射器。
Starting with version 2.2, the framework eagerly fetches IMAP messages and exposes them as an internal subclass of MimeMessage
.
This had the undesired side effect of changing the getContent()
behavior.
This inconsistency was further exacerbated by the Mail Mapping enhancement introduced in version 4.3, because, when a header mapper was provided, the payload was rendered by the IMAPMessage.getContent()
method.
This meant that the IMAP content differed, depending on whether a header mapper was provided.
从版本 5.0 开始,源自 IMAP 的消息会根据 IMAPMessage.getContent()
行为呈现其内容,无论是否提供了标题映射器。如果你不使用标题映射器,并且希望恢复到仅呈现正文的先前行为,请将邮件接收器上的布尔属性 simpleContent
设置为 true
. 无论是否使用标题映射器,此属性现在都控制渲染。现在在提供了标题映射器时它允许仅呈现正文。
Starting with version 5.0, messages originating from an IMAP source render the content in accordance with IMAPMessage.getContent()
behavior, regardless of whether a header mapper is provided.
If you do not use a header mapper, and you wish to revert to the previous behavior of rendering only the body, set the simpleContent
boolean property on the mail receiver to true
.
This property now controls the rendering regardless of whether a header mapper is used.
It now allows body-only rendering when a header mapper is provided.
从 5.2 版开始,邮件接收器上提供了 autoCloseFolder
选项。将其设置到 false
不会在获取后自动关闭文件夹,而是为通道适配器发送的每条消息填充一个 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
标头(有关更多详细信息,请参见 MessageHeaderAccessor
API)。这无法与 Pop3MailReceiver
一起使用,因为它依赖于打开和关闭文件夹以获取新消息。在下游流中每当需要时,目标应用程序负责调用此标头上的 close()
:
Starting with version 5.2, the autoCloseFolder
option is provided on the mail receiver.
Setting it to false
doesn’t close the folder automatically after a fetch, but instead an IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
header (see MessageHeaderAccessor
API for more information) is populated into every message to producer from the channel adapter.
This does not work with Pop3MailReceiver
as it relies on opening and closing the folder to get new messages.
It is the target application’s responsibility to call the close()
on this header whenever it is necessary in the downstream flow:
Closeable closeableResource = StaticMessageHeaderAccessor.getCloseableResource(mailMessage);
if (closeableResource != null) {
closeableResource.close();
}
在使用附件解析电子邮件的多部分内容期间需要与服务器通信时,保持文件夹处于打开状态非常有用。 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
头上的 close()
委托给 AbstractMailReceiver
,以使用 expunge
选项关闭文件夹,如果在 AbstractMailReceiver
上分别配置了 shouldDeleteMessages
。
Keeping the folder open is useful in cases where communication with the server is needed during parsing multipart content of the email with attachments.
The close()
on the IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
header delegates to the AbstractMailReceiver
to close the folder with expunge
option if shouldDeleteMessages
is configured respectively on the AbstractMailReceiver
.
从 5.4 版本开始,现在可以返回一个 MimeMessage
原样,没有任何转换或热切内容加载。通过以下选项组合启用了此功能:未提供 headerMapper
、 simpleContent
属性为 false
、 autoCloseFolder
属性为 false
。 MimeMessage
作为所生成 Spring 消息的有效载荷存在。在这种情况下,填充的唯一头是上面提到的用于文件夹的 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
,在 MimeMessage
处理完成时必须关闭文件夹。
Starting with version 5.4, it is possible now to return a MimeMessage
as is without any conversion or eager content loading.
This functionality is enabled with this combination of options: no headerMapper
provided, the simpleContent
property is false
and the autoCloseFolder
property is false
.
The MimeMessage
is present as the payload of the Spring message produced.
In this case, the only header populated is the mentioned above IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
for the folder which must be closed when processing of the MimeMessage
is complete.
从 5.5.11 版本开始,如果未收到消息或所有消息均被过滤掉,则在 AbstractMailReceiver.receive()
之后自动关闭文件夹,而与 autoCloseFolder
标志无关。在这种情况下,对于 IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
头周围的可能逻辑,下游没有任何要生成的内容。
Starting with version 5.5.11, the folder is closed automatically after AbstractMailReceiver.receive()
if no messages received or all of them are filtered out independently of the autoCloseFolder
flag.
In this case there is nothing to produce downstream for possible logic around IntegrationMessageHeaderAccessor.CLOSEABLE_RESOURCE
header.
从 6.0.5 版本开始, ImapIdleChannelAdapter
不再执行异步消息发布。这对于阻塞空闲侦听器循环以便在消息下游进行处理(例如,有大附件)是必需的,因为邮件文件夹必须保持打开状态。如果需要异步切换,可以使用 ExecutorChannel
作为该信道适配器的输出信道。
Starting with version 6.0.5, the ImapIdleChannelAdapter
no longer performs asynchronous message publishing.
This is necessary to block the idle listener loop for message processing downstream (e.g. with big attachments) because the mail folder must remain open.
If an async hand-off is required, an ExecutorChannel
can be used as the output channel of this channel adapter.
Inbound Mail Message Mapping
默认情况下,入站适配器所生成的消息的有效载荷是原始的 MimeMessage
。你可以使用该对象来查询头和内容。从 4.3 版本开始,你可以提供一个 HeaderMapper<MimeMessage>
来将头映射到 MessageHeaders
。为了方便起见,Spring Integration 提供了一个 DefaultMailHeaderMapper
以此为目的。它映射以下头:
By default, the payload of messages produced by the inbound adapters is the raw MimeMessage
.
You can use that object to interrogate the headers and content.
Starting with version 4.3, you can provide a HeaderMapper<MimeMessage>
to map the headers to MessageHeaders
.
For convenience, Spring Integration provides a DefaultMailHeaderMapper
for this purpose.
It maps the following headers:
-
mail_from
: AString
representation of thefrom
address. -
mail_bcc
: AString
array containing thebcc
addresses. -
mail_cc
: AString
array containing thecc
addresses. -
mail_to
: AString
array containing theto
addresses. -
mail_replyTo
: AString
representation of thereplyTo
address. -
mail_subject
: The mail subject. -
mail_lineCount
: A line count (if available). -
mail_receivedDate
: The received date (if available). -
mail_size
: The mail size (if available). -
mail_expunged
: A boolean indicating if the message is expunged. -
mail_raw
: AMultiValueMap
containing all the mail headers and their values. -
mail_contentType
: The content type of the original mail message. -
contentType
: The payload content type (see below).
启用消息映射时,有效载荷取决于邮件消息及其实现。电子邮件内容通常由 MimeMessage
中的 DataHandler
呈现。
When message mapping is enabled, the payload depends on the mail message and its implementation.
Email contents are usually rendered by a DataHandler
within the MimeMessage
.
对于 text/*
类型的电子邮件,有效载荷是一个 String
, contentType
头与 mail_contentType
相同。
For a text/*
email, the payload is a String
and the contentType
header is the same as mail_contentType
.
对于包含嵌入式 jakarta.mail.Part`实例的消息,`DataHandler`通常会呈现一个 `Part`对象。这些对象不是 `Serializable
,不适合使用备用技术(如 Kryo
)进行序列化。因此,默认情况下,当启用映射时,此类负载将呈现为包含 Part`数据的原始 `byte[]
。Part`的示例为 `Message`和 `Multipart
。在这种情况下,contentType`头为 `application/octet-stream
。要更改此行为并接收 Multipart`对象负载,请将 `embeddedPartsAsBytes`设置为 `MailReceiver`上的 `false
。对于 DataHandler`未知的内容类型,内容将呈现为具有 `application/octet-stream`的 `contentType`头的 `byte[]
。
For a messages with embedded jakarta.mail.Part
instances, the DataHandler
usually renders a Part
object.
These objects are not Serializable
and are not suitable for serialization with alternative technologies such as Kryo
.
For this reason, by default, when mapping is enabled, such payloads are rendered as a raw byte[]
containing the Part
data.
Examples of Part
are Message
and Multipart
.
The contentType
header is application/octet-stream
in this case.
To change this behavior and receive a Multipart
object payload, set embeddedPartsAsBytes
to false
on MailReceiver
.
For content types that are unknown to the DataHandler
, the contents are rendered as a byte[]
with a contentType
header of application/octet-stream
.
当你未提供头映射器时,消息有效载荷是 jakarta.mail
呈现的 MimeMessage
。该框架提供了一个 MailToStringTransformer
,你可以使用它通过使用战略将邮件内容转换为 String
来转换消息:
When you do not provide a header mapper, the message payload is the MimeMessage
presented by jakarta.mail
.
The framework provides a MailToStringTransformer
that you can use to convert the message by using a strategy to convert the mail contents to a 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
。
Starting with version 4.3, the transformer handles embedded Part
instances (as well as Multipart
instances, which were handled previously).
The transformer is a subclass of AbstractMailTransformer
that maps the address and subject headers from the preceding list.
If you wish to perform some other transformation on the message, consider subclassing AbstractMailTransformer
.
从 5.4 版本开始,当未提供 headerMapper
、 autoCloseFolder
为 false
、 simpleContent
为 false
时,返回的 MimeMessage
在所生成 Spring 消息的有效载荷中保持原样。这样,当稍后在流中引用 MimeMessage
的内容时,将按需加载。上述所有转换仍然有效。
Starting with version 5.4, when no headerMapper
is provided, autoCloseFolder
is false
and simpleContent
is false
, the MimeMessage
is returned as-is in the payload of the Spring message produced.
This way, the content of the MimeMessage
is loaded on demand when referenced, later in the flow.
All the mentioned above transformations are still valid.
Mail Namespace Support
Spring Integration 为与邮件相关的配置提供了一个命名空间。要使用它,请配置以下架构位置:
Spring Integration provides a namespace for mail-related configuration. To use it, configure the following schema locations:
<?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">
要配置出站信道适配器,请提供从其接收的信道和邮件发送器,如下例所示:
To configure an outbound channel adapter, provide the channel from which to receive and the MailSender, as the following example shows:
<int-mail:outbound-channel-adapter channel="outboundMail"
mail-sender="mailSender"/>
或者,你可以提供主机、用户名和密码,如下例所示:
Alternatively, you can provide the host, username, and password, as the following example shows:
<int-mail:outbound-channel-adapter channel="outboundMail"
host="somehost" username="someuser" password="somepassword"/>
从 5.1.3 版本开始,如果提供了 java-mail-properties
,则可以省略 host
、 username
和 mail-sender
。然而,必须使用适当的 Java 邮件属性配置 host
和 username
,例如对于 SMTP:
Starting with version 5.1.3, the host
, username
ane mail-sender
can be omitted, if java-mail-properties
is provided.
However, the host
and username
has to be configured with appropriate Java mail properties, e.g. for SMTP:
mail.user=someuser@gmail.com
mail.smtp.host=smtp.gmail.com
mail.smtp.port=587
与任何出站通道适配器一样,如果引用的通道是 |
As with any outbound Channel Adapter, if the referenced channel is a |
当你使用命名空间支持时,还可以使用 header-enricher
消息变换器。这样做简化了在发送到邮件出站信道适配器之前将前面提到的头应用于任何消息。
When you use the namespace support, you can also use a header-enricher
message transformer.
Doing so simplifies the application of the headers mentioned earlier to any message prior to sending to the mail outbound channel adapter.
以下示例假设有效载荷是 Java bean,且为指定属性提供了适当的 getter,但你可以使用任何 SpEL 表达式:
The following example assumes the payload is a Java bean with appropriate getters for the specified properties, but you can use any SpEL expression:
<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
属性,以控制现有标头的行为。
Alternatively, you can use the value
attribute to specify a literal.
You also can specify default-overwrite
and individual overwrite
attributes to control the behavior with existing headers.
若要配置入站通道适配器,你可以选择轮询或事件驱动(假设你的邮件服务器支持 IMAP idle
— 如果不支持,则轮询是唯一选项)。轮询通道适配器需要存储的 URI 以及用于发送入站邮件的通道。URI 可以以 pop3
或 imap
开头。以下示例使用 imap
URI:
To configure an inbound channel adapter, you have the choice between polling or event-driven (assuming your mail server supports IMAP idle
— if not, then polling is the only option).
A polling channel adapter requires the store URI and the channel to which to send inbound messages.
The URI may begin with pop3
or imap
.
The following example uses an 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
邮件通道:
If you do have IMAP idle
support, you may want to configure the imap-idle-channel-adapter
element instead.
Since the idle
command enables event-driven notifications, no poller is necessary for this adapter.
It sends a message to the specified channel as soon as it receives the notification that new mail is available.
The following example configures an IMAP idle
mail channel:
<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
命名空间。
You can provide javaMailProperties
by creating and populating a regular java.utils.Properties
object — for example, by using the util
namespace provided by Spring.
如果您的用户名包含 “@” 字符,请使用 “%40” 代替 “@”,以避免底层 JavaMail API 的解析错误。
If your username contains the '@' character, use '%40' instead of '@' to avoid parsing errors from the underlying JavaMail API.
以下示例演示如何配置 java.util.Properties
对象:
The following example shows how to configure a java.util.Properties
object:
<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
查找邮件,这是所有邮件:
By default, the ImapMailReceiver
searches for messages based on the default SearchTerm
, which is all mail messages that:
-
Are RECENT (if supported)
-
Are NOT ANSWERED
-
Are NOT DELETED
-
Are NOT SEEN
-
hHave not been processed by this mail receiver (enabled by the use of the custom USER flag or simply NOT FLAGGED if not supported)
自定义用户标志是 spring-integration-mail-adapter
,但你可以对其进行配置。自 2.2 版本以来,ImapMailReceiver
使用的 SearchTerm
由 SearchTermStrategy
完全配置,你可以使用 search-term-strategy
属性注入该策略。SearchTermStrategy
是一个策略接口,具有让你创建 ImapMailReceiver
使用的 SearchTerm
实例的单个方法。以下清单显示了 SearchTermStrategy
接口:
The custom user flag is spring-integration-mail-adapter
, but you can configure it.
Since version 2.2, the SearchTerm
used by the ImapMailReceiver
is fully configurable with SearchTermStrategy
, which you can inject by using the search-term-strategy
attribute.
A SearchTermStrategy
is a strategy interface with a single method that lets you create an instance of the SearchTerm
used by the ImapMailReceiver
.
The following listing shows the SearchTermStrategy
interface:
public interface SearchTermStrategy {
SearchTerm generateSearchTerm(Flags supportedFlags, Folder folder);
}
以下示例依赖于 TestSearchTermStrategy
而不是默认的 SearchTermStrategy
:
The following example relies on TestSearchTermStrategy
rather than the default 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"/>
有关消息标记的信息,请参见 Marking IMAP Messages When Recent
Is Not Supported。
See Marking IMAP Messages When Recent
Is Not Supported for information about message flagging.
从 4.1.1 版开始,IMAP 邮件接收器将使用 mail.imap.peek
或 mail.imaps.peek
JavaMail 属性,如果指定了该属性。以前,接收器会忽略该属性并始终设置 PEEK
标志。现在,如果你明确将此属性设置为 false
,则无论 shouldMarkMessagesRead
设置如何,邮件都会被标记为 \Seen
。如果没有指定,则保留以前的的行为(窥视为 true
)。
Starting with version 4.1.1, the IMAP mail receiver uses the mail.imap.peek
or mail.imaps.peek
JavaMail property, if specified.
Previously, the receiver ignored the property and always set the PEEK
flag.
Now, if you explicitly set this property to false
, the message ise marked as \Seen
regardless of the setting of shouldMarkMessagesRead
.
If not specified, the previous behavior is retained (peek is true
).
IMAP idle
and Lost Connections
使用 IMAP idle
通道适配器时,与服务器的连接可能会丢失(例如,通过网络故障),并且由于 JavaMail 文档明确指出实际 IMAP API 是实验性的,因此了解 API 中的差异以及如何配置 IMAP idle
适配器时处理这些差异非常重要。目前,Spring Integration 邮件适配器已使用 JavaMail 1.4.1 和 JavaMail 1.4.3 进行了测试。根据使用哪一个版本,你必须特别注意一些需要设置的 JavaMail 属性以便于自动重新连接。
When using an IMAP idle
channel adapter, connections to the server may be lost (for example, through network failure) and, since the JavaMail documentation explicitly states that the actual IMAP API is experimental, it is important to understand the differences in the API and how to deal with them when configuring IMAP idle
adapters.
Currently, Spring Integration mail adapters were tested with JavaMail 1.4.1 and JavaMail 1.4.3.
Depending on which one is used, you must pay special attention to some JavaMail properties that need to be set with regard to auto-reconnect.
在 Gmail 中观察到了以下行为,但应该提供一些提示,说明如何解决与其他提供商重新连接的问题。但是,总是欢迎反馈意见。同样地,以下注释基于 Gmail。 |
The following behavior was observed with Gmail but should provide you with some tips on how to solve re-connect issue with other providers. However, feedback is always welcome. Again, the following notes are based on 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
,或者简单返回,从而让你可以继续进行自动重新连接。目前,自动重新连接无限运行,每十秒尝试重新连接一次。
With JavaMail 1.4.1, if you set the mail.imaps.timeout
property to a relatively short period of time (approximately 5 min in our testing), IMAPFolder.idle()
throws FolderClosedException
after this timeout.
However, if this property is not set (it should be indefinite) the IMAPFolder.idle()
method never returns and never throws an exception.
It does, however, reconnect automatically if the connection was lost for a short period of time (under 10 min in our testing).
However, if the connection was lost for a long period of time (over 10 min), IMAPFolder.idle()
, does not throw FolderClosedException
and does not re-establish the connection, and remains in the blocked state indefinitely, thus leaving you no possibility to reconnect without restarting the adapter.
Consequently, the only way to make re-connecting work with JavaMail 1.4.1 is to set the mail.imaps.timeout
property explicitly to some value, but it also means that such value should be relatively short (under 10 min) and the connection should be re-established relatively quickly.
Again, it may be different with providers other than Gmail.
With JavaMail 1.4.3 introduced significant improvements to the API, ensuring that there is always a condition that forces the IMAPFolder.idle()
method to return StoreClosedException
or FolderClosedException
or to simply return, thus letting you proceed with auto-reconnecting.
Currently, auto-reconnecting runs infinitely making attempts to reconnect every ten seconds.
在这两种配置中,channel
和 should-delete-messages
都是必需属性。您应该理解为什么 should-delete-messages
是必需的。这个问题与 POP3 协议有关,它不知道已读邮件。它只能知道在单个会话中已读取的内容。这意味着,当您的 POP3 邮件适配器运行时,电子邮件将在每次轮询期间成功消耗,因为这些电子邮件变得可用,并且没有一封电子邮件被传递多次。但是,只要您重新启动适配器并开始新的会话,所有在前一会话中已被检索的电子邮件都会被再次检索。这就是 POP3 的性质。有些人可能会争论 should-delete-messages
应该默认情况下为 true
。换句话说,有两种有效且相互排斥的用途,这使得很难选择单个最佳默认值。您可能希望将适配器配置为唯一的电子邮件接收器,在这种情况下,您希望能够在不担心以前传递的邮件不再传递的情况下重新启动适配器。在这种情况下,将 should-delete-messages
设置为 true
是最合理的。但是,您可能还有另一种用例,您可能希望让多个适配器监视电子邮件服务器及其内容。换句话说,您希望“窥视而不接触”。然后将 should-delete-messages
设置为 false
更合适。因此,由于很难选择 should-delete-messages
属性的正确默认值,我们将其设为您需要设置的必需属性。将它留给您还意味着您不太可能最终产生意外行为。
In both configurations, channel
and should-delete-messages
are required attributes.
You should understand why should-delete-messages
is required.
The issue is with the POP3 protocol, which does not have any knowledge of messages that were read.
It can only know what has been read within a single session.
This means that, when your POP3 mail adapter runs, emails are successfully consumed as they become available during each poll and no single email message is delivered more then once.
However, as soon as you restart your adapter and begin a new session, all the email messages that might have been retrieved in the previous session are retrieved again.
That is the nature of POP3.
Some might argue that should-delete-messages
should be true
by default.
In other words, there are two valid and mutually exclusive use that make it very hard to pick a single best default.
You may want to configure your adapter as the only email receiver, in which case you want to be able to restart your adapter without fear that previously delivered messages are not delivered again.
In this case, setting should-delete-messages
to true
would make the most sense.
However, you may have another use case where you may want to have multiple adapters monitor email servers and their content.
In other words, you want to 'peek but not touch'.
Then setting should-delete-messages
to false
is much more appropriate.
So since it is hard to choose what should be the right default value for the should-delete-messages
attribute, we made it a required attribute to be set by you.
Leaving it up to you also means that you are less likely to end up with unintended behavior.
在配置轮询电子邮件适配器的 |
When configuring a polling email adapter’s |
在连接静默断开的情况下,空闲取消任务会在后台定期运行(通常会立即处理新的 IDLE)。为了控制这个时间间隔,提供了 cancelIdleInterval
选项;默认值为 120(2 分钟)。RFC 2177 建议时间间隔不超过 29 分钟。
In the case of a silently dropped connection, an idle cancel task is run in the background periodically (a new IDLE will usually immediately be processed).
To control this interval, a cancelIdleInterval
option is provided; default 120 (2 minutes).
RFC 2177 recommends an interval no larger than 29 minutes.
你应该明白,这些操作(标记邮件为已读和删除邮件)是在邮件被接收后但在被处理之前执行的。这可能导致丢失邮件。
You should understand that these actions (marking messages read and deleting messages) are performed after the messages are received but before they are processed. This can cause messages to be lost.
您可能希望考虑改用事务同步。请参阅 Transaction Synchronization。
You may wish to consider using transaction synchronization instead. See Transaction Synchronization.
<imap-idle-channel-adapter/>
也接受 error-channel
属性。如果抛出下游异常并指定了 error-channel
,则会将包含失败消息和原始异常的 MessagingException
消息发送到此通道。否则,如果下游通道是同步的,则通道适配器会将任何此类异常记录为警告。
The <imap-idle-channel-adapter/>
also accepts the 'error-channel' attribute.
If a downstream exception is thrown and an 'error-channel' is specified, a MessagingException
message containing the failed message and the original exception is sent to this channel.
Otherwise, if the downstream channels are synchronous, any such exception is logged as a warning by the channel adapter.
从 3.0 版本开始,IMAP |
Beginning with the 3.0 release, the IMAP |
Marking IMAP Messages When \Recent
Is Not Supported
如果 shouldMarkMessagesAsRead
为 true,则 IMAP 适配器会设置 \Seen
标志。
If shouldMarkMessagesAsRead
is true, the IMAP adapters set the \Seen
flag.
另外,当电子邮件服务器不支持 \Recent
标志时,IMAP 适配器会使用用户标志(默认情况下为 spring-integration-mail-adapter
)标记邮件,只要服务器支持用户标志。如果不支持,则将 Flag.FLAGGED
设置为 true
。这些标志会应用,而无论 shouldMarkMessagesRead
设置如何。
In addition, when an email server does not support the \Recent
flag, the IMAP adapters mark messages with a user flag (by default, spring-integration-mail-adapter
), as long as the server supports user flags.
If not, Flag.FLAGGED
is set to true
.
These flags are applied regardless of the shouldMarkMessagesRead
setting.
正如 null 中所讨论的,默认 SearchTermStrategy
会忽略标记为如此的消息。
As discussed in null, the default SearchTermStrategy
ignore messages that are so flagged.
从 4.2.2 版本开始,您可以通过 MailReceiver
中启用 setUserFlag
来设置用户标记的名称。这样做可以使多个接收者使用不同的标记(只要邮件服务器支持用户标记)。配置适配器时,user-flag
属性可以在命名空间中使用。
Starting with version 4.2.2, you can set the name of the user flag by using setUserFlag
on the MailReceiver
.
Doing so lets multiple receivers use a different flag (as long as the mail server supports user flags).
The user-flag
attribute is available when configuring the adapter with the namespace.
Email Message Filtering
很多情况下,您可能需要对传入的消息进行筛选(比如,您只想读取 Subject
行中包含“Spring Integration”的电子邮件)。可以通过将入站邮件适配器与基于表达式的 Filter
相连接实现这一目标。虽然这种方法可以实现目标,但它也有一定弊端。由于消息是在经过入站邮件适配器后进行筛选的,因此所有此类消息都被标记为已读 (SEEN
) 或未读(取决于 should-mark-messages-as-read
属性的值)。然而,实际上,仅当消息通过筛选条件时将其标记为 SEEN
会更有用。这类似于在预览窗格中滚动查看所有电子邮件时查看电子邮件客户端,但仅将实际上打开和标记为 SEEN
的消息标记为已读。
Very often, you may encounter a requirement to filter incoming messages (for example, you want to read only emails that have 'Spring Integration' in the Subject
line).
You can accomplish this by connecting an inbound mail adapter with an expression-based Filter
.
Although it would work, there is a downside to this approach.
Since messages would be filtered after going through the inbound mail adapter, all such messages would be marked as read (SEEN
) or unread (depending on the value of should-mark-messages-as-read
attribute).
However, in reality, it be more useful to mark messages as SEEN
only if they pass the filtering criteria.
This is similar to looking at your email client while scrolling through all the messages in the preview pane, but only flagging messages that were actually opened and read as 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.'
。
Spring Integration 2.0.4 introduced the mail-filter-expression
attribute on inbound-channel-adapter
and imap-idle-channel-adapter
.
This attribute lets you provide an expression that is a combination of SpEL and a regular expression.
For example if you would like to read only emails that contain 'Spring Integration' in the subject line, you would configure the mail-filter-expression
attribute like as follows: mail-filter-expression="subject matches '(?i).Spring Integration."
.
由于 jakarta.mail.internet.MimeMessage
是 SpEL 评估上下文的根上下文,您可以在任何可通过 MimeMessage
获得的值(包括消息的实际内容)的基础上进行筛选。这个功能尤其重要,因为读取消息内容通常会导致此类消息默认被标记为 SEEN
。不过,由于我们现在将所有传入消息的 PEEK
标志设置为“true”,因此只有明确标记为 SEEN
的消息才会被标记为已读。
Since jakarta.mail.internet.MimeMessage
is the root context of the SpEL evaluation context, you can filter on any value available through MimeMessage
, including the actual body of the message.
This one is particularly important, since reading the body of the message typically results in such messages being marked as SEEN
by default.
However, since we now set the PEEK
flag of every incoming message to 'true', only messages that were explicitly marked as SEEN
are marked as read.
因此,在以下示例中,只有与筛选表达式匹配的消息才会由此适配器输出,并且只有这些消息会被标记为已读:
So, in the following example, only messages that match the filter expression are output by this adapter and only those messages are marked as read:
<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”的消息。
In the preceding example, thanks to the mail-filter-expression
attribute, only messages that contain 'Spring Integration' in the subject line are produced by this adapter.
另一个合理的问题是下次轮询或空闲事件发生时会发生什么,或者在重新启动此类适配器时会发生什么。是否会出现要筛选的消息重复的问题?换句话说,如果您在上次检索中有五条新消息且只有一条通过了筛选,其他四条消息会怎样?它们是否会在下次轮询或空闲时再次通过筛选逻辑?毕竟它们并没有被标记为 SEEN
。答案是否定的。它们不会因为由电子邮件服务器设置并由 Spring Integration 邮件搜索筛选器使用的另一个标记 (RECENT
) 而受到重复处理。文件夹实现设置此标记以表明此消息对该文件夹而言是新的。也就是说,自上次打开此文件夹以来,此消息已到达。换句话说,虽然我们的适配器可能会窥视电子邮件,但它也会让电子邮件服务器知道已触及此电子邮件,因此电子邮件服务器会将其标记为 RECENT
。
Another reasonable question is what happens on the next poll or idle event or what happens when such an adapter is restarted.
Can there be duplication of massages to be filtered? In other words, if, on the last retrieval where you had five new messages and only one passed the filter, what would happen with the other four?
Would they go through the filtering logic again on the next poll or idle?
After all, they were not marked as SEEN
.
The answer is no.
They would not be subject to duplicate processing due to another flag (RECENT
) that is set by the email server and is used by the Spring Integration mail search filter.
Folder implementations set this flag to indicate that this message is new to this folder.
That is, it has arrived since the last time this folder was opened.
In other words, while our adapter may peek at the email, it also lets the email server know that such email was touched and should therefore be marked as RECENT
by the email server.
Transaction Synchronization
入站适配器的交易同步让您可以在交易提交或回滚后采取不同的操作。您可以通过将 <transactional/>
元素添加到轮询 <inbound-adapter/>
或 <imap-idle-inbound-adapter/>
的轮询器,来启用交易同步。即使没有涉及“实际”事务,您仍然可以通过使用包含 <transactional/>
元素的 PseudoTransactionManager
来启用此功能。有关详细信息,请参见 Transaction Synchronization。
Transaction synchronization for inbound adapters lets you take different actions after a transaction commits or rolls back.
You can enable transaction synchronization by adding a <transactional/>
element to the poller for the polled <inbound-adapter/>
or to the <imap-idle-inbound-adapter/>
.
Even if there is no 'real' transaction involved, you can still enable this feature by using a PseudoTransactionManager
with the <transactional/>
element.
For more information, see Transaction Synchronization.
由于有不同的邮件服务器,特别是某些服务器存在限制,因此我们目前仅为这些事务同步提供策略。您可以将消息发送给其他某些 Spring Integration 组件或调用自定义 Bean 以执行一些操作。例如,要在事务提交后将 IMAP 消息移到其他文件夹,您可以使用类似以下内容:
Because of the different mail servers and specifically the limitations that some have, at this time we provide only a strategy for these transaction synchronizations. You can send the messages to some other Spring Integration components or invoke a custom bean to perform some action. For example, to move an IMAP message to a different folder after the transaction commits, you might use something similar to the following:
<int-mail:imap-idle-channel-adapter id="customAdapter"
store-uri="imaps://something.com:password@imap.something.com/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
类的外观:
The following example shows what the Mover
class might look like:
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”。
For the message to be still available for manipulation after the transaction, should-delete-messages must be set to 'false'.
Configuring channel adapters with the Java DSL
要在 Java DSL 中配置邮件组件,该框架提供了一个 o.s.i.mail.dsl.Mail
工厂,其使用方法如下:
To configure mail component in Java DSL, the framework provides a o.s.i.mail.dsl.Mail
factory, which can be used like this:
@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();
}
}