FTP出站通道适配器

FTP出站通道适配器依赖于`MessageHandler`实现,该实现连接到FTP服务器并为收到的每个消息负载中的文件启动FTP传输。 它还支持文件的多种表示形式,因此您不仅限于`java.io.File`类型的文件负载。 FTP出站通道适配器支持以下负载:

  • java.io.File: 实际的文件对象

  • byte[]: 表示文件内容的字节数组

  • java.lang.String: 表示文件内容的文本

  • java.io.InputStream: 要传输到远程文件的数据流

  • org.springframework.core.io.Resource: 要传输到远程文件的数据资源

以下示例展示了如何配置`outbound-channel-adapter`:

<int-ftp:outbound-channel-adapter id="ftpOutbound"
    channel="ftpChannel"
    session-factory="ftpSessionFactory"
    charset="UTF-8"
    remote-file-separator="/"
    auto-create-directory="true"
    remote-directory-expression="headers['remote_dir']"
    temporary-remote-directory-expression="headers['temp_remote_dir']"
    filename-generator="fileNameGenerator"
    use-temporary-filename="true"
    chmod="600"
    mode="REPLACE"/>

前面的配置展示了如何使用`outbound-channel-adapter`元素配置FTP出站通道适配器,同时为各种属性(例如`filename-generator`(o.s.i.file.FileNameGenerator`策略接口的实现)、`session-factory`的引用以及其他属性)提供值。 您还可以看到一些*expression`属性的示例,这些属性允许您使用SpEL配置设置,例如`remote-directory-expression`、temporary-remote-directory-expression`和`remote-filename-generator-expression(`filename-generator`的SpEL替代方案,如前面的示例所示)。 与任何允许使用SpEL的组件一样,可以通过“payload”和“headers”变量访问负载和消息头。 有关可用属性的更多详细信息,请参阅schema

默认情况下,如果未指定文件名称生成器,Spring Integration会使用`o.s.i.file.DefaultFileNameGenerator`。 DefaultFileNameGenerator`根据`MessageHeaders`中`file_name`头的值(如果存在)确定文件名,或者,如果消息的负载已经是`java.io.File,则使用该文件的原始名称。

定义某些值(例如`remote-directory`)可能取决于平台或FTP服务器。 例如,正如在[role="bare"][role="bare"][role="bare"]forum.spring.io/showthread.php?p=333478&posted=1#post333478上报告的那样,在某些平台上,您必须在目录定义的末尾添加斜杠(例如,remote-directory="/thing1/thing2/"`而不是`remote-directory="/thing1/thing2")。

从版本4.1开始,您可以在传输文件时指定`mode`。 默认情况下,现有文件会被覆盖。 模式由`FileExistsMode`枚举定义,包括以下值:

  • REPLACE (默认)

  • REPLACE_IF_MODIFIED

  • APPEND

  • APPEND_NO_FLUSH

  • IGNORE

  • FAIL

IGNORE`和`FAIL`不传输文件。 `FAIL`会导致抛出异常,而`IGNORE`则静默忽略传输(尽管会生成`DEBUG`日志条目)。 版本5.2引入了`chmod`属性,您可以使用它在上传后更改远程文件权限。 您可以使用传统的Unix八进制格式(例如,`600`只允许文件所有者读写)。 在使用Java配置适配器时,您可以使用`setChmodOctal("600")`或`setChmod(0600)。 仅当您的FTP服务器支持`SITE CHMOD`子命令时才适用。

避免部分写入的文件

在处理文件传输时,一个常见问题是可能处理部分文件。 也就是说,文件可能在其传输实际完成之前出现在文件系统中。

为了解决这个问题,Spring Integration FTP适配器使用一种通用算法:文件以临时名称传输,然后在完全传输后重命名。

默认情况下,每个正在传输的文件都会在文件系统中出现一个附加后缀,默认情况下为`.writing`。 您可以通过设置`temporary-file-suffix`属性来更改此后缀。

但是,在某些情况下,您可能不想使用此技术(例如,如果服务器不允许重命名文件)。 对于这种情况,您可以通过将`use-temporary-file-name`设置为`false`来禁用此功能(默认值为`true`)。 当此属性为`false`时,文件以其最终名称写入,并且消费应用程序需要其他机制来检测文件完全上传后才能访问它。

使用Java配置进行配置

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

@SpringBootApplication
@IntegrationComponentScan
public class FtpJavaApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context =
                    new SpringApplicationBuilder(FtpJavaApplication.class)
                        .web(false)
                        .run(args);
        MyGateway gateway = context.getBean(MyGateway.class);
        gateway.sendToFtp(new File("/foo/bar.txt"));
    }

    @Bean
    public SessionFactory<FTPFile> ftpSessionFactory() {
        DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
        sf.setHost("localhost");
        sf.setPort(port);
        sf.setUsername("foo");
        sf.setPassword("foo");
        sf.setTestSession(true);
        return new CachingSessionFactory<FTPFile>(sf);
    }

    @Bean
    @ServiceActivator(inputChannel = "ftpChannel")
    public MessageHandler handler() {
        FtpMessageHandler handler = new FtpMessageHandler(ftpSessionFactory());
        handler.setRemoteDirectoryExpressionString("headers['remote-target-dir']");
        handler.setFileNameGenerator(new FileNameGenerator() {

            @Override
            public String generateFileName(Message<?> message) {
                 return "handlerContent.test";
            }

        });
        return handler;
    }

    @MessagingGateway
    public interface MyGateway {

         @Gateway(requestChannel = "toFtpChannel")
         void sendToFtp(File file);

    }
}

使用Java DSL进行配置

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

@SpringBootApplication
@IntegrationComponentScan
public class FtpJavaApplication {

    public static void main(String[] args) {
        ConfigurableApplicationContext context =
            new SpringApplicationBuilder(FtpJavaApplication.class)
                .web(false)
                .run(args);
        MyGateway gateway = context.getBean(MyGateway.class);
        gateway.sendToFtp(new File("/foo/bar.txt"));
    }

    @Bean
    public SessionFactory<FTPFile> ftpSessionFactory() {
        DefaultFtpSessionFactory sf = new DefaultFtpSessionFactory();
        sf.setHost("localhost");
        sf.setPort(port);
        sf.setUsername("foo");
        sf.setPassword("foo");
        sf.setTestSession(true);
        return new CachingSessionFactory<FTPFile>(sf);
    }

    @Bean
    public IntegrationFlow ftpOutboundFlow() {
        return IntegrationFlow.from("toFtpChannel")
                .handle(Ftp.outboundAdapter(ftpSessionFactory(), FileExistsMode.FAIL)
                        .useTemporaryFileName(false)
                        .fileNameExpression("headers['" + FileHeaders.FILENAME + "']")
                        .remoteDirectory(this.ftpServer.getTargetFtpDirectory().getName())
                ).get();
    }

    @MessagingGateway
    public interface MyGateway {

         @Gateway(requestChannel = "toFtpChannel")
         void sendToFtp(File file);

    }

}