如需使用最新稳定版本,请使用 Spring Integration 7.0.4spring-doc.cadn.net.cn

写入文件

要将消息写入文件系统,您可以使用 FileWritingMessageHandler。 此类可处理以下有效负载类型:spring-doc.cadn.net.cn

对于字符串负载,您可以配置编码和字符集。spring-doc.cadn.net.cn

为了简化操作,您可以使用 XML 命名空间将 FileWritingMessageHandler 配置为出站通道适配器或出站网关的一部分。spring-doc.cadn.net.cn

从版本 4.3 开始,您可以指定写入文件时使用的缓冲区大小。spring-doc.cadn.net.cn

从版本 5.1 开始,您可以提供一个 BiConsumer<File, Message<?>> newFileCallback,当使用 FileExistsMode.APPENDFileExistsMode.APPEND_NO_FLUSH 且需要创建新文件时触发该回调。 此回调会接收一个新创建的文件以及触发它的消息。 例如,可以使用此回调将消息头中定义的 CSV 表头写入文件。spring-doc.cadn.net.cn

生成文件名

在最简单的形式下,FileWritingMessageHandler只需要一个用于写入文件的目的地目录。 要写入的文件名由处理程序的FileNameGenerator决定。 默认实现会查找其键与定义为FileHeaders.FILENAME的常量相匹配的消息头。spring-doc.cadn.net.cn

或者,您可以指定一个表达式,该表达式将针对消息进行求值以生成文件名——例如,headers['myCustomHeader'] + '.something'。 该表达式的结果必须为 String。 为方便起见,DefaultFileNameGenerator 还提供了 setHeaderName 方法,允许您显式指定要用作文件名的消息头。spring-doc.cadn.net.cn

一旦设置完成,DefaultFileNameGenerator将采用以下解析步骤来确定给定消息负载的文件名:spring-doc.cadn.net.cn

  1. 评估该表达式相对于消息的结果,如果结果为非空的 String,则将其用作文件名。spring-doc.cadn.net.cn

  2. 否则,如果负载是 java.io.File,则使用 File 对象的文件名。spring-doc.cadn.net.cn

  3. 否则,使用附加了 .msg 的消息 ID 作为文件名。spring-doc.cadn.net.cn

当您使用 XML 命名空间支持时,文件出站通道适配器(file outbound channel adapter)和文件出站网关(file outbound gateway)均支持以下互斥的配置属性:spring-doc.cadn.net.cn

写入文件时,会使用临时文件后缀(默认为.writing)。 在文件写入过程中,该后缀会附加到文件名之后。 若要自定义该后缀,您可以在文件出站通道适配器(file outbound channel adapter)和文件出站网关(file outbound gateway)上设置temporary-file-suffix属性。spring-doc.cadn.net.cn

当使用 APPEND 文件 mode 时,temporary-file-suffix 属性会被忽略,因为数据是直接追加到文件中的。

从版本 4.2.5 开始,生成的文件名(作为filename-generatorfilename-generator-expression求值的结果)可以表示子路径与目标文件名的组合。 它继续作为File(File parent, String child)的第二个构造函数参数使用。 然而,过去我们并未为子路径创建mkdirs()目录,而仅假设了文件名。 这种方法在需要恢复文件系统树以匹配源目录的情况下非常有用——例如,在解压归档文件并将所有文件按原始顺序保存到目标目录时。spring-doc.cadn.net.cn

指定输出目录

文件出站通道适配器与文件出站网关均提供两个互斥的配置属性,用于指定输出目录:spring-doc.cadn.net.cn

Spring Integration 2.2 引入了 directory-expression 属性。

使用directory属性

当您使用 directory 属性时,输出目录被设置为一个固定值,该值在 FileWritingMessageHandler 初始化时设定。 如果您未指定此属性,则必须使用 directory-expression 属性。spring-doc.cadn.net.cn

使用directory-expression属性

如果您想要完整的 SpEL 支持,可以使用 directory-expression 属性。 该属性接受一个 SpEL 表达式,该表达式会在处理每条消息时进行求值。 因此,当动态指定输出文件目录时,您可以完全访问消息的负载(payload)及其头部信息。spring-doc.cadn.net.cn

SpEL 表达式必须解析为 Stringjava.io.Fileorg.springframework.core.io.Resource。 (后者无论如何都会被评估为 File。) 此外,生成的 StringFile 必须指向一个目录。 如果您未指定 directory-expression 属性,则必须设置 directory 属性。spring-doc.cadn.net.cn

使用auto-create-directory属性

默认情况下,如果目标目录不存在,则会自动创建相应的目标目录以及任何不存在的父目录。 若要阻止此行为,您可以将 auto-create-directory 属性设置为 false。 此属性同时适用于 directorydirectory-expression 属性。spring-doc.cadn.net.cn

当使用 directory 属性且 auto-create-directoryfalse 时,自 Spring Integration 2.2 起进行了以下更改:spring-doc.cadn.net.cn

不再在适配器初始化时检查目标目录是否存在,而是改为在处理每条消息时进行检查。spring-doc.cadn.net.cn

此外,如果 auto-create-directorytrue 且在消息处理之间目录被删除,则每个正在处理的消息都会重新创建该目录。spring-doc.cadn.net.cn

处理现有目标文件

当您写入文件且目标文件已存在时,默认行为是覆盖该目标文件。 您可以通过在相关的文件出站组件上设置 mode 属性来更改此行为。 存在以下选项:spring-doc.cadn.net.cn

Spring Integration 2.2 引入了 mode 属性以及 APPENDFAILIGNORE 选项。
REPLACE

如果目标文件已存在,则会被覆盖。 如果未指定 mode 属性,则在写入文件时这是默认行为。spring-doc.cadn.net.cn

REPLACE_IF_MODIFIED

如果目标文件已存在,仅当最后修改时间戳与源文件不同时才会覆盖它。 对于 File 负载,将 lastModified 次的时间与现有文件进行比较。 对于其他负载,将 FileHeaders.SET_MODIFIED (file_setModified) 头与现有文件进行比较。 如果该头缺失或值不是 Number,则始终替换该文件。spring-doc.cadn.net.cn

APPEND

此模式允许您将消息内容追加到现有文件中,而不是每次都创建新文件。 请注意,此属性与 temporary-file-suffix 属性互斥,因为当它将内容追加到现有文件时,适配器不再使用临时文件。 每条消息处理完毕后,文件将被关闭。spring-doc.cadn.net.cn

APPEND_NO_FLUSH

此选项具有与APPEND相同的语义,但在每条消息后不会刷新数据且不会关闭文件。 这可以在发生故障导致数据丢失的风险下提供显著的性能提升。 有关更多信息,请参阅使用APPEND_NO_FLUSH时刷新文件spring-doc.cadn.net.cn

FAIL

如果目标文件存在,将抛出 MessageHandlingExceptionspring-doc.cadn.net.cn

IGNORE

如果目标文件已存在,消息负载将被静默忽略。spring-doc.cadn.net.cn

当使用临时文件后缀(默认为.writing)时,如果最终文件名或临时文件名已存在,则IGNORE选项适用。

使用 Spring 框架时刷新文件APPEND_NO_FLUSH

APPEND_NO_FLUSH模式是在版本4.3中引入的。 使用它可以提高性能,因为文件在每条消息后不会被关闭。 然而,这可能会导致在发生故障时数据丢失。spring-doc.cadn.net.cn

Spring Integration 提供了多种刷新策略以减轻数据丢失:spring-doc.cadn.net.cn

  • 使用 flushInterval。 如果在此时间段内未写入文件,则会自动刷新。 这只是一个近似值,最多可能达到 1.33x 次(平均为 1.167x)。spring-doc.cadn.net.cn

  • 向消息处理器的trigger方法发送包含正则表达式的消息。 匹配该模式的绝对路径文件将被刷新。spring-doc.cadn.net.cn

  • 提供处理程序的一个自定义 MessageFlushPredicate 实现,以修改当消息发送到 trigger 方法时所采取的操作。spring-doc.cadn.net.cn

  • 通过传入自定义的 flushIfNeededFileWritingMessageHandler.MessageFlushPredicate 实现来调用处理器的 FileWritingMessageHandler.FlushPredicate 方法之一。spring-doc.cadn.net.cn

谓词会对每个打开的文件进行调用。 有关这些接口的更多信息,请参阅 Javadoc。 请注意,自 5.0 版本起,谓词方法提供了另一个参数:如果是新文件或之前已关闭的文件,则为其首次写入的时间。spring-doc.cadn.net.cn

当使用 flushInterval 时,间隔从最后一次写入开始计算。 仅当文件在间隔时间内处于空闲状态时才会进行刷新。 从版本 4.3.7 开始,可以设置一个额外的属性 (flushWhenIdle) 为 false,表示间隔从对之前已刷新(或新建)文件的**首次**写入开始计算。spring-doc.cadn.net.cn

文件时间戳

默认情况下,目标文件的 lastModified 时间戳为文件创建的时间(就地重命名除外,此时保留当前时间戳)。 从 4.3 版本开始,您可以配置 preserve-timestamp(在使用 Java 配置时则为 setPreserveTimestamp(true))。 对于 File 类型的负载,这会将时间戳从入站文件传输到出站文件(无论是否需要复制)。 对于其他类型的负载,如果存在 FileHeaders.SET_MODIFIED 头信息(file_setModified),则使用该头信息来设置目标文件的 lastModified 时间戳,前提是该头信息的值为 Numberspring-doc.cadn.net.cn

文件权限

从版本 5.0 开始,当向支持 POSIX 权限的文件系统写入文件时,您可以在出站通道适配器或网关中指定这些权限。 该属性是一个整数,通常以熟悉的八进制格式提供——例如,0640,表示所有者具有读/写权限,组具有只读权限,其他人没有访问权限。spring-doc.cadn.net.cn

文件出站通道适配器

以下示例配置了一个文件出站通道适配器:spring-doc.cadn.net.cn

<int-file:outbound-channel-adapter id="filesOut" directory="${input.directory.property}"/>

基于命名空间的配置还支持 delete-source-files 属性。 如果设置为 true,它将在写入目标后触发删除原始源文件。 该标志的默认值为 false。 以下示例展示了如何将其设置为 truespring-doc.cadn.net.cn

<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选项:spring-doc.cadn.net.cn

<int-file:outbound-channel-adapter id="newlineAdapter"
	append-new-line="true"
    directory="${output.directory}"/>

出站网关

在需要根据已写入的文件继续处理消息的情况下,您可以使用outbound-gateway代替。 它起到与outbound-channel-adapter类似的作用。 不过,在写入文件后,它还会将文件作为消息的负载发送到回复通道。spring-doc.cadn.net.cn

以下示例配置了一个出站网关:spring-doc.cadn.net.cn

<int-file:outbound-gateway id="mover" request-channel="moveInput"
    reply-channel="output"
    directory="${output.directory}"
    mode="REPLACE" delete-source-files="true"/>

如前所述,您还可以指定 mode 属性,该属性定义了当目标文件已存在时如何处理这种情况。 有关更多详细信息,请参阅 处理已存在的目标文件。 通常,在使用文件出站网关时,结果文件会作为回复通道上的消息负载返回。spring-doc.cadn.net.cn

这也适用于指定 IGNORE 模式的情况。 在这种情况下,将返回预先存在的目标文件。 如果请求消息的有效载荷是一个文件,您仍然可以通过消息头访问该原始文件。 请参阅 FileHeaders.ORIGINAL_FILEspring-doc.cadn.net.cn

'outbound-gateway'在您需要先移动文件,然后将其通过处理管道的情况下工作良好。 在这种情况下,您可以将文件命名空间的inbound-channel-adapter元素连接到outbound-gateway,然后将该网关的reply-channel连接到管道的开头。

如果您有更复杂的需求,或者需要支持额外的负载类型作为输入以转换为文件内容,您可以扩展FileWritingMessageHandler,但更好的选择是依赖Transformerspring-doc.cadn.net.cn

使用 Java 配置进行配置

以下 Spring Boot 应用示例展示了如何使用 Java 配置来配置入站适配器:spring-doc.cadn.net.cn

@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 配置入站适配器:spring-doc.cadn.net.cn

@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();
    }

}