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元素,同时还为各种属性(例如filename-generator(o.s.i.file.FileNameGeneratorstrategy 接口)、对session-factory和其他属性。
您还可以看到一些*expression属性,这些属性允许您使用 SPEL 配置设置,例如remote-directory-expression,temporary-remote-directory-expression和remote-filename-generator-expression(SPEL 的替代品filename-generator,如前面的示例所示)。
与任何允许使用 SPEL 的组件一样,可以通过“payload”和“headers”变量访问有效负载和消息 Headers。
请参阅 架构 有关可用属性的更多详细信息。
| 默认情况下,如果未指定文件名生成器,则 Spring 集成使用 o.s.i.file.DefaultFileNameGenerator.DefaultFileNameGenerator根据file_name标头(如果存在)在MessageHeaders,或者,如果 Message 的有效负载已经是java.io.File,则使用该文件的原始名称。 | 
| 定义某些值(例如 remote-directory) 可能依赖于平台或 FTP 服务器。
例如,正如 forum.spring.io/showthread.php?p=333478&posted=1#post333478 上报告的那样,在某些平台上,您必须在目录定义的末尾添加斜杠(例如,remote-directory="/thing1/thing2/"而不是remote-directory="/thing1/thing2"). | 
从版本 4.1 开始,您可以指定mode传输文件时。
默认情况下,现有文件将被覆盖。
模式由FileExistsModeenumeration,其中包括以下值:
- 
REPLACE(默认)
- 
REPLACE_IF_MODIFIED
- 
APPEND
- 
APPEND_NO_FLUSH
- 
IGNORE
- 
FAIL
IGNORE和FAIL请勿传输文件。FAIL导致引发异常,而IGNORE以 Silent Json 的形式忽略传输(尽管DEBUGlog entry 生成)。
版本 5.2 引入了chmod属性,可用于在上传后更改远程文件权限。
您可以使用传统的 Unix 八进制格式(例如600仅允许文件所有者的读写)。
使用 java 配置适配器时,您可以使用setChmodOctal("600")或setChmod(0600).
仅当您的 FTP 服务器支持SITE CHMOD子命令。
避免部分写入的文件
处理文件传输时出现的常见问题之一是处理部分文件的可能性。 也就是说,文件可能在传输实际完成之前就出现在文件系统中。
为了解决这个问题, Spring 集成 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);
    }
}