此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Integration 6.3.1! |
此版本仍在开发中,尚未被视为稳定版本。对于最新的稳定版本,请使用 Spring Integration 6.3.1! |
若要将消息写入文件系统,可以使用 FileWritingMessageHandler
。
此类可以处理以下有效负载类型:
-
File
-
String
-
字节数组
-
InputStream
(自 4.2 版起)
对于 String 有效负载,您可以配置编码和字符集。
为了简化操作,可以使用 XML 命名空间将 配置为出站通道适配器或出站网关的一部分。FileWritingMessageHandler
从版本 4.3 开始,您可以指定写入文件时要使用的缓冲区大小。
从版本 5.1 开始,您可以提供在使用 or 时触发的 WHICH 并且必须创建新文件。
此回调接收新创建的文件和触发它的消息。
例如,此回调可用于编写消息标头中定义的 CSV 标头。BiConsumer<File, Message<?>>
newFileCallback
FileExistsMode.APPEND
FileExistsMode.APPEND_NO_FLUSH
生成文件名
在最简单的形式中,只需要一个目标目录来写入文件。
要写入的文件的名称由处理程序的 FileNameGenerator
确定。
默认实现查找其键与定义为 FileHeaders.FILENAME
的常量匹配的消息标头。FileWritingMessageHandler
或者,您可以指定要根据消息计算的表达式以生成文件名,例如 .
表达式的计算结果必须为 .
为方便起见,还提供了该方法,允许您显式指定要用作文件名的值的消息标头。headers['myCustomHeader'] + '.something'
String
DefaultFileNameGenerator
setHeaderName
设置完成后,将采用以下解析步骤来确定给定消息有效负载的文件名:DefaultFileNameGenerator
-
根据消息计算表达式,如果结果为非空,则将其用作文件名。
String
-
否则,如果有效负载为 ,则使用对象的文件名。
java.io.File
File
-
否则,请使用附加了 的消息 ID。作为文件名。
msg
使用 XML 命名空间支持时,文件出站通道适配器和文件出站网关都支持以下互斥配置属性:
-
filename-generator
(对实现的引用)FileNameGenerator
-
filename-generator-expression
(计算结果为String
)
写入文件时,使用临时文件后缀(默认为 )。
在写入文件时,它将追加到文件名中。
若要自定义后缀,可以在文件出站通道适配器和文件出站网关上设置该属性。.writing
temporary-file-suffix
使用 file 时,该属性将被忽略,因为数据直接追加到文件中。APPEND mode temporary-file-suffix |
从版本 4.2.5 开始,生成的文件名(作为结果或评估)可以与目标文件名一起表示子路径。
它和以前一样用作第二个构造函数参数。
但是,在过去,我们没有为子路径创建 () 目录,仅假设文件名。
当我们需要恢复文件系统树以匹配源目录时,这种方法非常有用,例如,在解压缩存档并按原始顺序保存目标目录中的所有文件时。filename-generator
filename-generator-expression
File(File parent, String child)
mkdirs()
使用 file 时,该属性将被忽略,因为数据直接追加到文件中。APPEND mode temporary-file-suffix |
指定输出目录
文件出站通道适配器和文件出站网关都提供了两个互斥的配置属性,用于指定输出目录:
-
directory
-
directory-expression
Spring Integration 2.2 引入了该属性。directory-expression |
使用属性directory
使用该属性时,输出目录将设置为固定值,该值在初始化时设置。
如果未指定此属性,则必须使用该属性。directory
FileWritingMessageHandler
directory-expression
使用属性directory-expression
如果要获得完整的 SpEL 支持,可以使用该属性。
此属性接受针对正在处理的每条消息计算的 SpEL 表达式。
因此,当您动态指定输出文件目录时,您可以完全访问消息的有效负载及其标头。directory-expression
SpEL 表达式必须解析为 或 。
(后者无论如何都被计算成一个。
此外,生成的 or 必须指向目录。
如果未指定属性,则必须设置属性。String
java.io.File
org.springframework.core.io.Resource
File
String
File
directory-expression
directory
使用属性auto-create-directory
默认情况下,如果目标目录不存在,则会自动创建相应的目标目录和任何不存在的父目录。
若要防止该行为,可以将属性设置为 。
此属性适用于 和 属性。auto-create-directory
false
directory
directory-expression
使用 和 是 属性时,从 Spring Integration 2.2 开始进行了以下更改: 现在,不再在初始化适配器时检查目标目录是否存在,而是对正在处理的每条消息执行此检查。 此外,如果 is 并且在处理消息之间删除了目录,则会为正在处理的每条消息重新创建该目录。 |
Spring Integration 2.2 引入了该属性。directory-expression |
使用 和 是 属性时,从 Spring Integration 2.2 开始进行了以下更改: 现在,不再在初始化适配器时检查目标目录是否存在,而是对正在处理的每条消息执行此检查。 此外,如果 is 并且在处理消息之间删除了目录,则会为正在处理的每条消息重新创建该目录。 |
处理现有目标文件
当您写入文件并且目标文件已存在时,默认行为是覆盖该目标文件。
您可以通过在相关文件出站组件上设置属性来更改此行为。
存在以下选项:mode
-
REPLACE
(默认) -
REPLACE_IF_MODIFIED
-
APPEND
-
APPEND_NO_FLUSH
-
FAIL
-
IGNORE
Spring Integration 2.2 引入了属性和 、 和 选项。mode APPEND FAIL IGNORE |
REPLACE
-
如果目标文件已存在,则会覆盖它。 如果未指定该属性,则这是写入文件时的默认行为。
mode
REPLACE_IF_MODIFIED
-
如果目标文件已存在,则仅当上次修改的时间戳与源文件的时间戳不同时,才会覆盖该文件。 对于有效负载,将有效负载时间与现有文件进行比较。 对于其他有效负载,将 () 标头与现有文件进行比较。 如果标头缺失或具有不是 的值,则始终会替换该文件。
File
lastModified
FileHeaders.SET_MODIFIED
file_setModified
Number
APPEND
-
此模式允许您将消息内容追加到现有文件,而不是每次都创建一个新文件。 请注意,此属性与该属性互斥,因为当它将内容追加到现有文件时,适配器将不再使用临时文件。 每封邮件后,文件将关闭。
temporary-file-suffix
APPEND_NO_FLUSH
-
此选项具有与 相同的语义,但不会刷新数据,并且不会在每条消息后关闭文件。 这可以提供显着的性能,但在发生故障时存在数据丢失的风险。 有关详细信息,请参阅使用
APPEND_NO_FLUSH
时刷新文件。APPEND
FAIL
-
如果目标文件存在,则会引发
MessageHandlingException
。 IGNORE
-
如果目标文件存在,则以静默方式忽略消息有效负载。
使用临时文件后缀(默认为 )时,如果最终文件名或临时文件名存在,则该选项将适用。.writing IGNORE |
Spring Integration 2.2 引入了属性和 、 和 选项。mode APPEND FAIL IGNORE |
使用临时文件后缀(默认为 )时,如果最终文件名或临时文件名存在,则该选项将适用。.writing IGNORE |
使用时刷新文件APPEND_NO_FLUSH
该模式是在版本 4.3 中添加的。
使用它可以提高性能,因为文件不会在每条消息后关闭。
但是,如果发生故障,这可能会导致数据丢失。APPEND_NO_FLUSH
Spring Integration 提供了几种刷新策略来缓解此数据丢失:
-
用。 如果文件在此时间段内未写入,则会自动刷新该文件。 这是近似值,可能到这个时候为止(平均值为 )。
flushInterval
1.33x
1.167x
-
将包含正则表达式的消息发送到消息处理程序的方法。 具有与模式匹配的绝对路径名的文件将被刷新。
trigger
-
为处理程序提供自定义实现,以修改向方法发送消息时执行的操作。
MessageFlushPredicate
trigger
-
通过传入自定义项或实现来调用处理程序的方法之一。
flushIfNeeded
FileWritingMessageHandler.FlushPredicate
FileWritingMessageHandler.MessageFlushPredicate
为每个打开的文件调用谓词。 有关更多信息,请参阅这些接口的 Javadoc。 请注意,从 5.0 版开始,谓词方法提供了另一个参数:如果是新文件或以前关闭的当前文件,则首次写入的时间。
使用 时,间隔从最后一次写入开始。
仅当文件在时间间隔内处于空闲状态时,才会刷新文件。
从版本 4.3.7 开始,可以将附加属性 () 设置为 ,这意味着间隔从第一次写入以前刷新(或新)的文件开始。flushInterval
flushWhenIdle
false
文件时间戳
默认情况下,目标文件的时间戳是创建文件的时间(但就地重命名保留当前时间戳除外)。
从 V4.3 开始,您现在可以配置(或使用 Java 配置时)。
对于有效负载,这会将时间戳从入站文件传输到出站文件(无论是否需要副本)。
对于其他有效负载,如果存在标头 (),则它用于设置目标文件的时间戳,只要标头是 .lastModified
preserve-timestamp
setPreserveTimestamp(true)
File
FileHeaders.SET_MODIFIED
file_setModified
lastModified
Number
文件权限
从 V5.0 开始,将文件写入支持 Posix 权限的文件系统时,可以在出站通道适配器或网关上指定这些权限。
该属性是一个整数,通常以熟悉的八进制格式提供,例如,表示所有者具有读/写权限,组具有只读权限,而其他人没有访问权限。0640
文件出站通道适配器
以下示例配置文件出站通道适配器:
<int-file:outbound-channel-adapter id="filesOut" directory="${input.directory.property}"/>
基于命名空间的配置还支持属性。
如果设置为 ,则在写入目标后触发删除原始源文件。
该标志的默认值为 。
下面的示例演示如何将其设置为:delete-source-files
true
false
true
<int-file:outbound-channel-adapter id="filesOut"
directory="${output.directory}"
delete-source-files="true"/>
仅当入站邮件具有有效负载或标头值包含源实例或表示原始文件路径时,该属性才有效。delete-source-files File FileHeaders.ORIGINAL_FILE File String |
从版本 4.2 开始,支持一个选项。
如果设置为 ,则在写入消息后,将向文件追加一个新行。
默认属性值为 。
以下示例演示如何使用该选项:FileWritingMessageHandler
append-new-line
true
false
append-new-line
<int-file:outbound-channel-adapter id="newlineAdapter"
append-new-line="true"
directory="${output.directory}"/>
仅当入站邮件具有有效负载或标头值包含源实例或表示原始文件路径时,该属性才有效。delete-source-files File FileHeaders.ORIGINAL_FILE File String |
出站网关
如果要继续根据写入的文件处理消息,则可以改用 。
它的作用类似于 .
但是,在写入文件后,它还会将其作为消息的有效负载发送到回复通道。outbound-gateway
outbound-channel-adapter
以下示例配置出站网关:
<int-file:outbound-gateway id="mover" request-channel="moveInput"
reply-channel="output"
directory="${output.directory}"
mode="REPLACE" delete-source-files="true"/>
如前所述,您还可以指定属性,该属性定义如何处理目标文件已存在的情况的行为。
有关详细信息,请参阅处理现有目标文件。
通常,使用文件出站网关时,结果文件将作为消息负载返回到应答通道上。mode
这也适用于指定模式。
在这种情况下,将返回预先存在的目标文件。
如果请求消息的有效负载是文件,您仍然可以通过消息标头访问该原始文件。
请参见FileHeaders.ORIGINAL_FILE。IGNORE
“出站网关”在您希望首先移动文件然后通过处理管道发送的情况下效果很好。
在这种情况下,您可以将文件命名空间的元素连接到管道的开头,然后将该网关连接到管道的开头。inbound-channel-adapter outbound-gateway reply-channel |
如果您有更复杂的要求,或者需要支持其他有效负载类型作为输入以转换为文件内容,则可以扩展 ,但更好的选择是依赖 Transformer
。FileWritingMessageHandler
“出站网关”在您希望首先移动文件然后通过处理管道发送的情况下效果很好。
在这种情况下,您可以将文件命名空间的元素连接到管道的开头,然后将该网关连接到管道的开头。inbound-channel-adapter outbound-gateway reply-channel |
使用 Java 配置进行配置
以下 Spring Boot 应用程序显示了如何使用 Java 配置配置入站适配器的示例:
@SpringBootApplication
@IntegrationComponentScan
public class FileWritingJavaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(FileWritingJavaApplication.class)
.web(false)
.run(args);
MyGateway gateway = context.getBean(MyGateway.class);
gateway.writeToFile("foo.txt", new File(tmpDir.getRoot(), "fileWritingFlow"), "foo");
}
@Bean
@ServiceActivator(inputChannel = "writeToFileChannel")
public MessageHandler fileWritingMessageHandler() {
Expression directoryExpression = new SpelExpressionParser().parseExpression("headers.directory");
FileWritingMessageHandler handler = new FileWritingMessageHandler(directoryExpression);
handler.setFileExistsMode(FileExistsMode.APPEND);
return handler;
}
@MessagingGateway(defaultRequestChannel = "writeToFileChannel")
public interface MyGateway {
void writeToFile(@Header(FileHeaders.FILENAME) String fileName,
@Header(FileHeaders.FILENAME) File directory, String data);
}
}
使用 Java DSL 进行配置
以下 Spring Boot 应用程序显示了如何使用 Java DSL 配置入站适配器的示例:
@SpringBootApplication
public class FileWritingJavaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(FileWritingJavaApplication.class)
.web(false)
.run(args);
MessageChannel fileWritingInput = context.getBean("fileWritingInput", MessageChannel.class);
fileWritingInput.send(new GenericMessage<>("foo"));
}
@Bean
public IntegrationFlow fileWritingFlow() {
return IntegrationFlow.from("fileWritingInput")
.enrichHeaders(h -> h.header(FileHeaders.FILENAME, "foo.txt")
.header("directory", new File(tmpDir.getRoot(), "fileWritingFlow")))
.handle(Files.outboundGateway(m -> m.getHeaders().get("directory")))
.channel(MessageChannels.queue("fileWritingResultChannel"))
.get();
}
}