跳到主要内容

读取文件

QWen Plus 中英对照 Reading Files

FileReadingMessageSource 可用于从文件系统中消费文件。这是 MessageSource 的一个实现,它从文件系统目录创建消息。以下示例展示了如何配置 FileReadingMessageSource

<bean id="pollableFileSource"
class="org.springframework.integration.file.FileReadingMessageSource"
p:directory="${input.directory}"/>
xml

要防止为某些文件创建消息,你可以提供一个 FileListFilter。默认情况下,我们使用以下过滤器:

  • IgnoreHiddenFileListFilter

  • AcceptOnceFileListFilter

IgnoreHiddenFileListFilter 确保隐藏文件不会被处理。请注意,隐藏的确切定义取决于系统。例如,在基于 UNIX 的系统上,以句点字符开头的文件被视为隐藏文件。而 Microsoft Windows 有一个专门的文件属性来标识隐藏文件。

important

4.2 版引入了 IgnoreHiddenFileListFilter。在之前的版本中,隐藏文件会被包含。使用默认配置时,IgnoreHiddenFileListFilter 会首先被触发,然后是 AcceptOnceFileListFilter

AcceptOnceFileListFilter 确保文件只从目录中被选取一次。

备注

AcceptOnceFileListFilter 将其状态存储在内存中。如果您希望状态能在系统重启后仍然存在,可以使用 FileSystemPersistentAcceptOnceFileListFilter。此过滤器将接受的文件名存储在 MetadataStore 实现中(请参阅 元数据存储)。此过滤器根据文件名和修改时间进行匹配。

自 4.0 版起,此过滤器需要一个 ConcurrentMetadataStore。当与共享数据存储(例如带有 RedisMetadataStoreRedis)一起使用时,它允许在多个应用程序实例或跨网络文件共享被多个服务器使用之间共享过滤键。

自 4.1.5 版起,此过滤器新增了一个属性 (flushOnUpdate),这会在每次更新时刷新元数据存储(如果存储实现了 Flushable)。

持久文件列表过滤器现在有一个布尔属性 forRecursion。将此属性设置为 true,也会设置 alwaysAcceptDirectories,这意味着在出站网关 (lsmget) 上的递归操作每次都将遍历整个目录树。这是为了解决目录树深处的变化未被检测到的问题。此外,forRecursion=true 会导致使用文件的完整路径作为元数据存储键;这解决了如果不同目录中出现同名文件时过滤器无法正常工作的问题。重要提示:这意味着持久元数据存储中现有的键将无法找到顶级目录下的文件。因此,默认情况下该属性为 false;这可能会在未来版本中更改。

以下示例配置了一个带有过滤器的 FileReadingMessageSource

<bean id="pollableFileSource"
class="org.springframework.integration.file.FileReadingMessageSource"
p:inputDirectory="${input.directory}"
p:filter-ref="customFilterBean"/>
xml

在读取文件时,一个常见的问题是文件可能在尚未准备好时就被检测到(也就是说,其他进程可能仍在写入该文件)。默认的 AcceptOnceFileListFilter 无法防止这种情况。在大多数情况下,如果文件写入进程在文件准备好读取时立即重命名每个文件,则可以防止这种情况。由默认的 AcceptOnceFileListFilter 和仅接受已准备好文件(可能是基于已知后缀)的 filename-patternfilename-regex 过滤器组合而成的过滤器,可以处理这种情况。CompositeFileListFilter 启用了这种组合,如下例所示:

<bean id="pollableFileSource"
class="org.springframework.integration.file.FileReadingMessageSource"
p:inputDirectory="${input.directory}"
p:filter-ref="compositeFilter"/>

<bean id="compositeFilter"
class="org.springframework.integration.file.filters.CompositeFileListFilter">
<constructor-arg>
<list>
<bean class="o.s.i.file.filters.AcceptOnceFileListFilter"/>
<bean class="o.s.i.file.filters.RegexPatternFileListFilter">
<constructor-arg value="^test.*$"/>
</bean>
</list>
</constructor-arg>
</bean>
xml

如果无法使用临时名称创建文件并重命名为最终名称,Spring Integration 提供了另一种替代方案。4.2 版添加了 LastModifiedFileListFilter。此过滤器可以配置一个 age 属性,使得只有比这个值更旧的文件才能通过过滤器。默认情况下,年龄为 60 秒,但你应该选择一个足够大的年龄以避免过早地获取文件(例如,由于网络故障)。以下示例展示了如何配置 LastModifiedFileListFilter

<bean id="filter" class="org.springframework.integration.file.filters.LastModifiedFileListFilter">
<property name="age" value="120" />
</bean>
xml

从 4.3.7 版本开始,引入了 ChainFileListFilterCompositeFileListFilter 的扩展),以允许在后续过滤器仅应看到前一个过滤器的结果的情况下使用。 (使用 CompositeFileListFilter 时,所有过滤器都能看到所有文件,但它只通过已通过所有过滤器的文件)。需要新行为的一个例子是 LastModifiedFileListFilterAcceptOnceFileListFilter 的组合,在这种情况下,我们不希望在一定时间间隔之前接受该文件。使用 CompositeFileListFilter 时,由于 AcceptOnceFileListFilter 在第一次遍历时会看到所有文件,因此当其他过滤器通过时它不会再次通过。CompositeFileListFilter 方法在模式过滤器与自定义过滤器结合使用以查找指示文件传输完成的辅助文件时非常有用。模式过滤器可能只会通过主文件(例如 something.txt),但“完成”过滤器需要查看(例如)something.done 是否存在。

假设我们有文件 a.txta.doneb.txt

模式过滤器只通过 a.txtb.txt,而 “done” 过滤器看到所有三个文件,只通过 a.txt。复合过滤器的最终结果是只有 a.txt 被释放。

备注

使用 ChainFileListFilter 时,如果链中的任何过滤器返回一个空列表,则不会调用剩余的过滤器。

版本 5.0 引入了 ExpressionFileListFilter,用于针对文件作为上下文评估根对象执行 SpEL 表达式。为此,所有用于文件处理的 XML 组件(本地和远程)以及现有的 filter 属性,都提供了 filter-expression 选项,如下例所示:

<int-file:inbound-channel-adapter
directory="${inputdir}"
filter-expression="name matches '.text'"
auto-startup="false"/>
xml

版本 5.0.5 引入了 DiscardAwareFileListFilter 实现,这些实现对被拒绝的文件感兴趣。为此,此类过滤器实现应通过 addDiscardCallback(Consumer<File>) 提供回调。在框架中,此功能由 FileReadingMessageSource.WatchServiceDirectoryScanner 使用,与 LastModifiedFileListFilter 结合使用。与常规的 DirectoryScanner 不同,WatchService 根据目标文件系统上的事件提供文件以进行处理。在轮询包含这些文件的内部队列时,LastModifiedFileListFilter 可能会因为它们相对于其配置的 age 来说太新而丢弃它们。因此,我们失去了该文件,无法在未来可能的考虑中使用它。弃用回调钩子让我们可以在内部队列中保留该文件,以便在后续轮询中根据 age 进行检查。CompositeFileListFilter 也实现了 DiscardAwareFileListFilter 并为所有其 DiscardAwareFileListFilter 委托填充弃用回调。

备注

由于 CompositeFileListFilter 会将文件与所有委托进行匹配,因此 discardCallback 可能会被调用多次针对同一个文件。

从 5.1 版本开始,FileReadingMessageSource 不会在目录存在的情况下进行检查,也不会在它的 start() 被调用之前创建它(通常是通过包装的 SourcePollingChannelAdapter)。以前,当引用目录时,例如在测试中,或者在稍后应用权限时,没有简单的方法可以防止操作系统权限错误。

消息头

从 5.0 版本开始,FileReadingMessageSource(除了作为轮询 Filepayload)会将以下 headers 填充到 outbound Message

  • FileHeaders.FILENAME:文件要发送的 File.getName()。可用于后续的重命名或复制逻辑。

  • FileHeaders.ORIGINAL_FILEFile 对象本身。通常,当我们在处理过程中丢失了原始 File 对象时,此头部由框架组件(如 splitterstransformers)自动填充。然而,为了与其他自定义用例保持一致和方便,此头部对于获取原始文件非常有用。

  • FileHeaders.RELATIVE_PATH:这是一个新引入的头部,用于表示相对于扫描根目录的文件路径部分。当需要在其他地方还原源目录层次结构时,此头部非常有用。为此,可以配置 DefaultFileNameGenerator(参见“`生成文件名)以使用此头部。

目录扫描和轮询

FileReadingMessageSource 不会立即为来自目录的文件生成消息。它使用内部队列来存放由 scanner 返回的“符合条件的文件”。scanEachPoll 选项用于确保在每次轮询时,内部队列都会用最新的输入目录内容刷新。默认情况下 (scanEachPoll = false),FileReadingMessageSource 在再次扫描目录之前会清空其队列。这种默认行为对于减少对包含大量文件的目录进行扫描特别有用。然而,在需要自定义排序的情况下,设置此标志为 true 的影响必须加以考虑。文件的处理顺序可能不会如预期那样。默认情况下,队列中的文件按其自然 (路径) 顺序处理。即使队列中已经有文件,通过扫描添加的新文件也会插入到适当的位置以保持这种自然顺序。为了自定义顺序,FileReadingMessageSource 可以接受一个 Comparator<File> 作为构造函数参数。它被内部 (PriorityBlockingQueue) 使用,以根据业务需求重新排序其内容。因此,要按照特定顺序处理文件,您应该向 FileReadingMessageSource 提供一个比较器,而不是对自定义 DirectoryScanner 产生的列表进行排序。

Version 5.0 引入了 RecursiveDirectoryScanner 以执行文件树遍历。实现基于 Files.walk(Path start, int maxDepth, FileVisitOption…​ options) 功能。根目录 (DirectoryScanner.listFiles(File)) 参数被排除在结果之外。所有其他子目录的包含和排除基于目标 FileListFilter 实现。例如,默认情况下,SimplePatternFileListFilter 会过滤掉目录。有关更多信息,请参阅 AbstractDirectoryAwareFileListFilter 及其实现。

备注

从 5.5 版本开始,Java DSL 的 FileInboundChannelAdapterSpec 提供了一个方便的 recursive(boolean) 选项,可以在目标 FileReadingMessageSource 中使用 RecursiveDirectoryScanner,而不是默认的那个。

命名空间支持

通过使用文件特定的命名空间,可以简化文件读取的配置。要这样做,请使用以下模板:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-file="http://www.springframework.org/schema/integration/file"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/integration
https://www.springframework.org/schema/integration/spring-integration.xsd
http://www.springframework.org/schema/integration/file
https://www.springframework.org/schema/integration/file/spring-integration-file.xsd">
</beans>
xml

在此命名空间中,您可以减少 FileReadingMessageSource 并将其包装在一个入站通道适配器中,如下所示:

<int-file:inbound-channel-adapter id="filesIn1"
directory="file:${input.directory}" prevent-duplicates="true" ignore-hidden="true"/>

<int-file:inbound-channel-adapter id="filesIn2"
directory="file:${input.directory}"
filter="customFilterBean" />

<int-file:inbound-channel-adapter id="filesIn3"
directory="file:${input.directory}"
filename-pattern="test*" />

<int-file:inbound-channel-adapter id="filesIn4"
directory="file:${input.directory}"
filename-regex="test[0-9]+\.txt" />
xml

第一个通道适配器示例依赖于默认的 FileListFilter 实现:

  • IgnoreHiddenFileListFilter (不处理隐藏文件)

  • AcceptOnceFileListFilter (防止重复)

因此,你也可以省略 prevent-duplicatesignore-hidden 属性,因为它们默认是 true

important

Spring Integration 4.2 引入了 ignore-hidden 属性。在之前的版本中,隐藏文件会被包含。

第二个通道适配器示例使用了一个自定义过滤器,第三个使用 filename-pattern 属性添加了一个基于 AntPathMatcher 的过滤器,第四个使用 filename-regex 属性为 FileReadingMessageSource 添加了一个基于正则表达式模式的过滤器。filename-patternfilename-regex 属性各自与普通的 filter 引用属性互斥。但是,您可以使用 filter 属性引用一个 CompositeFileListFilter 实例,该实例可以组合任意数量的过滤器,包括一个或多个基于模式的过滤器以满足您的特定需求。

当多个进程从同一目录读取时,您可能需要锁定文件以防止它们被并发获取。为此,您可以使用 FileLocker。有一个基于 java.nio 的实现可用,但也可以实现您自己的锁定方案。nio 锁可以通过以下方式注入:

<int-file:inbound-channel-adapter id="filesIn"
directory="file:${input.directory}" prevent-duplicates="true">
<int-file:nio-locker/>
</int-file:inbound-channel-adapter>
xml

你可以按照以下方式配置自定义锁柜:

<int-file:inbound-channel-adapter id="filesIn"
directory="file:${input.directory}" prevent-duplicates="true">
<int-file:locker ref="customLocker"/>
</int-file:inbound-channel-adapter>
xml
备注

当文件入站适配器配置了锁定器时,它会在允许接收文件之前负责获取锁。它不承担解锁文件的责任。如果你已经处理了文件并且保持锁处于挂起状态,那么你就会有内存泄漏。如果这是一个问题,你应该在适当的时间自行调用 FileLocker.unlock(File file)

当过滤和锁定文件不够用时,您可能需要完全控制文件的列出方式。为了实现此类需求,您可以使用 DirectoryScanner 的实现。此扫描程序让您确定每次轮询时列出的确切文件。这也就是 Spring Integration 内部用于将 FileListFilter 实例和 FileLocker 连接到 FileReadingMessageSource 的接口。您可以将自定义的 DirectoryScanner 注入到 <int-file:inbound-channel-adapter/>scanner 属性中,如下例所示:

<int-file:inbound-channel-adapter id="filesIn" directory="file:${input.directory}"
scanner="customDirectoryScanner"/>
xml

这样做给你完全的自由来选择排序、列出和锁定策略。

了解过滤器(包括 patternsregexprevent-duplicates 等)和 locker 实例实际上是被 scanner 使用的这一点很重要。适配器上设置的任何这些属性都会随后注入到内部的 scanner 中。对于外部的 scanner,所有过滤器和锁定器属性在 FileReadingMessageSource 上都是禁止的。如果需要的话,它们必须在自定义的 DirectoryScanner 上指定。换句话说,如果你将一个 scanner 注入到 FileReadingMessageSource 中,你应该在这个 scanner 上提供 filterlocker,而不是在 FileReadingMessageSource 上。

备注

默认情况下,DefaultDirectoryScanner 使用 IgnoreHiddenFileListFilterAcceptOnceFileListFilter。要防止使用它们,你可以配置自己的过滤器(例如 AcceptAllFileListFilter)或甚至将其设置为 null

WatchServiceDirectoryScanner

FileReadingMessageSource.WatchServiceDirectoryScanner 依赖于文件系统事件来检测新文件添加到目录。在初始化期间,目录会被注册以生成事件。初始文件列表也在初始化期间构建。在遍历目录树时,遇到的任何子目录也会被注册以生成事件。在第一次轮询时,会返回通过遍历目录获得的初始文件列表。在后续的轮询中,会返回来自新创建事件的文件。如果添加了新的子目录,则使用其创建事件遍历新的子树以查找现有文件并注册任何找到的新子目录。

备注

WatchKey 内部事件 queue 没有被程序以与目录修改事件发生一样快的速度清空时,会出现一个问题。如果队列大小超出限制,会发出一个 StandardWatchEventKinds.OVERFLOW 以表明可能丢失了一些文件系统事件。在这种情况下,根目录将被完全重新扫描。为了避免重复,请考虑使用适当的 FileListFilter(例如 AcceptOnceFileListFilter)或在处理完成后删除文件。

可以通过 FileReadingMessageSource.use-watch-service 选项启用 WatchServiceDirectoryScanner,该选项与 scanner 选项互斥。为提供的 directory 填充一个内部的 FileReadingMessageSource.WatchServiceDirectoryScanner 实例。

此外,现在 WatchService 轮询逻辑可以跟踪 StandardWatchEventKinds.ENTRY_MODIFYStandardWatchEventKinds.ENTRY_DELETE

如果你需要跟踪现有文件以及新文件的修改,你应该在 FileListFilter 中实现 ENTRY_MODIFY 事件的逻辑。否则,这些事件中的文件将被以相同的方式处理。

ResettableFileListFilter 实现会捕获 ENTRY_DELETE 事件。因此,它们的文件会被提供给 remove() 操作。当启用此事件时,像 AcceptOnceFileListFilter 这样的过滤器会将文件移除。结果是,如果出现同名文件,它将通过过滤器并作为消息发送。

为此,引入了 watch-events 属性 (FileReadingMessageSource.setWatchEvents(WatchEventType…​ watchEvents))。(WatchEventTypeFileReadingMessageSource 中的一个公共内部枚举。) 通过这种选项,我们可以为新文件使用一个下游流逻辑,并为修改过的文件使用其他逻辑。以下示例展示了如何在同一个目录中为创建和修改事件配置不同的逻辑:

值得一提的是,ENTRY_DELETE 事件会涉及到被监控目录的子目录重命名操作。更具体地说,与旧目录名称相关的 ENTRY_DELETE 事件会在通知新(重命名)目录的 ENTRY_CREATE 事件之前发生。在某些操作系统(如 Windows)上,必须注册 ENTRY_DELETE 事件来处理这种情况。否则,在文件资源管理器中重命名被监控的子目录可能会导致该子目录中的新文件无法被检测到。

<int-file:inbound-channel-adapter id="newFiles"
directory="${input.directory}"
use-watch-service="true"/>

<int-file:inbound-channel-adapter id="modifiedFiles"
directory="${input.directory}"
use-watch-service="true"
filter="acceptAllFilter"
watch-events="MODIFY"/> <!-- The default is CREATE. -->
xml

从 6.1 版本开始,FileReadingMessageSource 暴露了两个新的与 WatchService 相关的选项:

  • watchMaxDepth - 用于 Files.walkFileTree(Path root, Set attributes, int maxDepth, FileVisitor visitor) API 的参数;

  • watchDirPredicate - 一个 Predicate<Path>,用于测试扫描树中的目录是否应被遍历并使用 WatchService 和配置的监视事件类型进行注册。

限制内存消耗

你可以使用 HeadDirectoryScanner 来限制保留在内存中的文件数量。这在扫描大型目录时非常有用。通过 XML 配置,可以通过设置入站通道适配器上的 queue-size 属性来启用此功能。

在 4.2 版本之前,此设置与其他任何过滤器的使用不兼容。其他任何过滤器(包括 prevent-duplicates="true")会覆盖用于限制大小的过滤器。

备注

HeadDirectoryScanner 的使用与 AcceptOnceFileListFilter 不兼容。由于在轮询决策期间会咨询所有过滤器,AcceptOnceFileListFilter 并不知道其他过滤器可能会临时过滤文件。即使之前由 HeadDirectoryScanner.HeadFilter 过滤的文件现在可用,AcceptOnceFileListFilter 仍然会过滤它们。

通常,在这种情况下,你应该删除已处理的文件,而不是使用 AcceptOnceFileListFilter,以便以前被过滤的文件可以在未来的轮询中可用。

使用 Java 配置进行配置

以下 Spring Boot 应用程序展示了如何使用 Java 配置来配置 outbound 适配器的示例:

@SpringBootApplication
public class FileReadingJavaApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(FileReadingJavaApplication.class)
.web(false)
.run(args);
}

@Bean
public MessageChannel fileInputChannel() {
return new DirectChannel();
}

@Bean
@InboundChannelAdapter(value = "fileInputChannel", poller = @Poller(fixedDelay = "1000"))
public MessageSource<File> fileReadingMessageSource() {
FileReadingMessageSource source = new FileReadingMessageSource();
source.setDirectory(new File(INBOUND_PATH));
source.setFilter(new SimplePatternFileListFilter("*.txt"));
return source;
}

@Bean
@Transformer(inputChannel = "fileInputChannel", outputChannel = "processFileChannel")
public FileToStringTransformer fileToStringTransformer() {
return new FileToStringTransformer();
}

}
java

使用 Java DSL 进行配置

以下的 Spring Boot 应用程序展示了如何使用 Java DSL 配置 outbound 适配器的示例:

@SpringBootApplication
public class FileReadingJavaApplication {

public static void main(String[] args) {
new SpringApplicationBuilder(FileReadingJavaApplication.class)
.web(false)
.run(args);
}

@Bean
public IntegrationFlow fileReadingFlow() {
return IntegrationFlow
.from(Files.inboundAdapter(new File(INBOUND_PATH))
.patternFilter("*.txt"),
e -> e.poller(Pollers.fixedDelay(1000)))
.transform(Files.toStringTransformer())
.channel("processFileChannel")
.get();
}

}
java

'tail’ing 文件

另一种流行的用例是从文件的末尾(或尾部)获取 '行',在新增加行时捕获这些新行。提供了两种实现方式。第一种,OSDelegatingFileTailingMessageProducer,使用原生的 tail 命令(在具有该命令的操作系统上)。这在那些平台上通常是效率最高的实现方式。对于没有 tail 命令的操作系统,第二种实现方式,ApacheCommonsFileTailingMessageProducer,使用 Apache 的 commons-io Tailer 类。

在这两种情况下,文件系统事件(例如文件不可用和其他事件)会通过正常的 Spring 事件发布机制以 ApplicationEvent 实例的形式发布。此类事件的示例包括以下:

[message=tail: cannot open '/tmp/somefile' for reading:
No such file or directory, file=/tmp/somefile]

[message=tail: '/tmp/somefile' has become accessible, file=/tmp/somefile]

[message=tail: '/tmp/somefile' has become inaccessible:
No such file or directory, file=/tmp/somefile]

[message=tail: '/tmp/somefile' has appeared;
following end of new file, file=/tmp/somefile]
bash

前面示例中显示的事件序列可能会在文件旋转时发生。

从 5.0 版本开始,当文件在 idleEventInterval 期间没有数据时,会发出 FileTailingIdleEvent。以下示例显示了此类事件的外观:

[message=Idle timeout, file=/tmp/somefile] [idle time=5438]
bash
备注

并非所有支持 tail 命令的平台都提供这些状态消息。

从这些端点发出的消息具有以下标头:

  • FileHeaders.ORIGINAL_FILE: 文件对象

  • FileHeaders.FILENAME: 文件名 (File.getName())

备注

在 5.0 版之前 的版本中,FileHeaders.FILENAME 标头包含文件绝对路径的字符串表示。您现在可以通过对原始文件标头调用 getAbsolutePath() 来获取该字符串表示。

以下示例使用默认选项创建一个原生适配器('-F -n 0',表示从当前结尾跟随文件名)。

<int-file:tail-inbound-channel-adapter id="native"
channel="input"
task-executor="exec"
file="/tmp/foo"/>
xml

以下示例创建了一个带有 -F -n +0 选项的原生适配器(表示跟踪文件名,输出所有现有行)。

<int-file:tail-inbound-channel-adapter id="native"
channel="input"
native-options="-F -n +0"
task-executor="exec"
file-delay=10000
file="/tmp/foo"/>
xml

如果 tail 命令失败(在某些平台上,缺少文件会导致 tail 失败,即使指定了 -F),则该命令每 10 秒重试一次。

默认情况下,原生适配器从标准输出捕获并将内容作为消息发送。它们还从标准错误捕获以触发事件。从 4.3.6 版本开始,你可以通过将 enable-status-reader 设置为 false 来丢弃标准错误事件,如下例所示:

<int-file:tail-inbound-channel-adapter id="native"
channel="input"
enable-status-reader="false"
task-executor="exec"
file="/tmp/foo"/>
xml

在以下示例中,IdleEventInterval 被设置为 5000,这意味着如果五秒内没有写入任何行,则每五秒触发一次 FileTailingIdleEvent

<int-file:tail-inbound-channel-adapter id="native"
channel="input"
idle-event-interval="5000"
task-executor="exec"
file="/tmp/somefile"/>
xml

当你需要停止适配器时,这可能会很有用。

以下示例创建了一个 Apache commons-io Tailer 适配器,该适配器每两秒检查一次文件的新行,并每十秒检查一次缺失文件的存在:

<int-file:tail-inbound-channel-adapter id="apache"
channel="input"
task-executor="exec"
file="/tmp/bar"
delay="2000"
end="false" // <1>
reopen="true" // <2>
file-delay="10000"/>
xml
  • 文件是从开头开始追加的 (end="false"),而不是从结尾开始(这是默认行为)。

  • 每个块都会重新打开文件(默认行为是保持文件打开)。

important

指定 delayendreopen 属性会强制使用 Apache commons-io 适配器,并使 native-options 属性不可用。

处理不完整数据

在文件传输场景中,一个常见的问题是如何确定传输已完成,这样你就不会开始读取一个不完整的文件。解决这个问题的常见技术是以临时名称写入文件,然后原子地将其重命名为最终名称。这项技术,加上一个防止临时文件被消费者获取的过滤器,提供了一个健壮的解决方案。Spring Integration 的组件(无论是本地还是远程写文件)都使用了这项技术。默认情况下,它们会在文件名后追加 .writing,并在传输完成后将其移除。

另一种常见的技术是编写第二个“标记”文件,以指示文件传输已完成。在这种情况下,直到 somefile.txt.complete 也出现之前,你不应认为 somefile.txt (例如)可以使用。Spring Integration 5.0 版引入了新的过滤器来支持此机制。为文件系统 (FileSystemMarkerFilePresentFileListFilter)、FTPSFTP 提供了实现。它们是可配置的,使得标记文件可以具有任何名称,尽管它通常与正在传输的文件有关。有关更多信息,请参阅 Javadoc