写入文件

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

对于 String 负载,您可以配置编码和 charset。spring-doc.cadn.net.cn

为了简化作,您可以配置FileWritingMessageHandler作为出站通道适配器或出站网关的一部分,通过使用 XML 名称空间。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. 默认实现会查找其 key 与定义为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作为文件名。spring-doc.cadn.net.cn

当您使用 XML 名称空间支持时,文件出站通道适配器和文件出站网关都支持以下互斥的配置属性:spring-doc.cadn.net.cn

在写入文件时,将使用临时文件后缀(其默认值为.writing). 在写入文件时,它会附加到文件名中。 要自定义后缀,您可以设置temporary-file-suffixFile outbound Channel Adapter 和 File Outbound Gateway 上的属性。spring-doc.cadn.net.cn

使用APPEND文件modetemporary-file-suffix属性,因为数据是直接附加到文件中的。

从版本 4.2.5 开始,生成的文件名(作为filename-generatorfilename-generator-expressionevaluation) 可以表示子路径和目标文件名。 它用作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 表达式,该表达式针对正在处理的每条消息进行评估。 因此,当您动态指定输出文件目录时,您对消息的有效负载及其标头具有完全访问权限。spring-doc.cadn.net.cn

SPEL 表达式必须解析为String,java.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

使用directoryattribute 和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属性和APPEND,FAILIGNORE选项。
REPLACE

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

REPLACE_IF_MODIFIED

如果目标文件已存在,则仅当上次修改的时间戳与源文件的时间戳不同时,才会覆盖该目标文件。 为Filepayloads,则 payloadlastModified将 time 与现有文件进行比较。 对于其他负载,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

如果目标文件存在,则MessageHandlingException被抛出。spring-doc.cadn.net.cn

IGNORE

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

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

使用 时刷新文件APPEND_NO_FLUSH

APPEND_NO_FLUSHmode 是在版本 4.3 中添加的。 使用它可以提高性能,因为文件不会在每条消息后关闭。 但是,如果发生故障,这可能会导致数据丢失。spring-doc.cadn.net.cn

Spring 集成提供了几种刷新策略来减轻这种数据丢失: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

  • 调用处理程序的flushIfNeeded方法,方法是传入自定义FileWritingMessageHandler.FlushPredicateFileWritingMessageHandler.MessageFlushPredicate实现。spring-doc.cadn.net.cn

将为每个打开的文件调用谓词。 有关更多信息,请参阅这些接口的 Javadoc。 请注意,从版本 5.0 开始,谓词方法提供了另一个参数:当前文件的首次写入时间(如果是新的或以前的关闭)。spring-doc.cadn.net.cn

使用flushInterval,则间隔从上次写入开始。 仅当文件在时间间隔内处于空闲状态时,才会刷新该文件。 从版本 4.3.7 开始,一个额外的属性 (flushWhenIdle) 可以设置为false,这意味着该间隔从第一次写入以前刷新的(或新)文件开始。spring-doc.cadn.net.cn

文件时间戳

默认情况下,目标文件的lastModifiedtimestamp 是创建文件的时间(就地重命名保留当前时间戳除外)。 从版本 4.3 开始,您现在可以配置preserve-timestamp(或setPreserveTimestamp(true)使用 Java 配置时)。 为Filepayloads 的 Payloads,这会将时间戳从入站文件传输到出站文件(无论是否需要副本)。 对于其他负载,如果FileHeaders.SET_MODIFIED标头 (file_setModified) 的 API 中,它用于设置目标文件的lastModifiedtimestamp,只要标头是Number.spring-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. 以下示例演示如何将其设置为true:spring-doc.cadn.net.cn

<int-file:outbound-channel-adapter id="filesOut"
    directory="${output.directory}"
    delete-source-files="true"/>
delete-source-files属性仅在入站消息具有Filepayload 的 intent 或FileHeaders.ORIGINAL_FILEheader 值包含 sourceFile实例或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属性,该属性定义如何处理目标文件已存在的情况的行为。 有关更多详细信息,请参阅 Dealing with Existing Destination Files。 通常,在使用文件出站网关时,结果文件将作为回复通道上的消息负载返回。spring-doc.cadn.net.cn

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

“outbound-gateway” 在您希望首先移动文件,然后通过处理管道发送文件的情况下效果很好。 在这种情况下,您可以将文件命名空间的inbound-channel-adapter元素添加到outbound-gateway然后连接该网关的reply-channel拖动到管道的开头。

如果您有更详细的要求或需要支持其他有效负载类型作为要转换为文件内容的输入,则可以扩展FileWritingMessageHandler,但更好的选择是依赖Transformer.spring-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();
    }

}