FTP Outbound Channel Adapter
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 会使用 |
定义某些值(如 remote-directory
)可能取决于平台或 FTP 服务器。例如,正如 [role="bare"][role="bare"]https://forum.spring.io/showthread.php?p=333478&posted=1#post333478 所报告的,在某些平台上,您必须在目录定义末尾添加一个斜杠(例如,remote-directory="/thing1/thing2/"
而不是 remote-directory="/thing1/thing2"
)。
从版本 4.1 开始,你可以在传输文件时指定 mode
。默认情况下,将覆盖现有文件。这些模式由 FileExistsMode
枚举定义,其中包括以下值:
-
REPLACE
(default) -
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
子命令时才适用。
Avoiding Partially Written Files
在处理文件传输时遇到的常见问题之一是可能处理部分文件。也就是说,文件可能在传输实际完成之前出现在文件系统中。
为了解决这个问题,Spring Integration FTP 适配器使用了通用算法:在临时名称下传输文件,然后在完全传输完成后重命名它们。
默认情况下,正在传输过程中的每个文件都会以一个附加后缀出现在文件系统中,默认情况下为 .writing
。你可以通过设置 temporary-file-suffix
属性来更改此后缀。
然而,可能存在一些不希望使用此技术的情况(例如,服务器不允许重命名文件)。对于此类情况,可以通过将 use-temporary-file-name
设置为 false
(默认值为 true
)来禁用此功能。当此属性为 false
时,文件将以其最终名称写入,并且使用应用程序需要其他机制在访问文件之前检测文件是否完全上传。
Configuring with Java Configuration
以下 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);
}
}
Configuring with the 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);
}
}