DefaultFtpSessionFactory提供底层客户端 API 的抽象,该 API(从 Spring Integration 2.0 开始)是 Apache Commons Net。 这样可以使您免于 . 会话工厂上公开了几个通用属性(从 4.0 版开始,现在包括 、 和 )。 但是,您有时需要访问较低级别的配置才能实现更高级的配置(例如,将端口范围设置为活动模式)。 为此,(所有 FTP 会话工厂的基类)以以下列表中所示的两种后处理方法的形式公开钩子:org.apache.commons.net.ftp.FTPClientconnectTimeoutdefaultTimeoutdataTimeoutFTPClientAbstractFtpSessionFactorySpring中文文档

/**
 * Will handle additional initialization after client.connect() method was invoked,
 * but before any action on the client has been taken
 */
protected void postProcessClientAfterConnect(T t) throws IOException {
    // NOOP
}
/**
 * Will handle additional initialization before client.connect() method was invoked.
 */
protected void postProcessClientBeforeConnect(T client) throws IOException {
    // NOOP
}

如您所见,这两种方法没有默认实现。 但是,通过扩展 ,可以重写这些方法以提供 的更高级配置,如以下示例所示:DefaultFtpSessionFactoryFTPClientSpring中文文档

public class AdvancedFtpSessionFactory extends DefaultFtpSessionFactory {

    protected void postProcessClientBeforeConnect(FTPClient ftpClient) throws IOException {
       ftpClient.setActivePortRange(4000, 5000);
    }
}

FTPS 和共享 SSLSession

使用 FTP over SSL 或 TLS 时,某些服务器要求在控件和数据连接上使用相同的 FTP。 这是为了防止“窃取”数据连接。 有关详细信息,请参阅 scarybeastsecurity.blogspot.cz/2009/02/vsftpd-210-released.htmlSSLSessionSpring中文文档

目前,Apache FTPSClient 不支持此功能。 请参阅 NET-408Spring中文文档

以下解决方案由 Stack Overflow 提供,它使用 对 的反射,因此它可能不适用于其他 JVM。 堆栈溢出答案是在 2015 年提交的,该解决方案已由 Spring Integration 团队在 JDK 1.8.0_112 上进行了测试。sun.security.ssl.SSLSessionContextImplSpring中文文档

以下示例演示如何创建 FTPS 会话:Spring中文文档

@Bean
public DefaultFtpsSessionFactory sf() {
    DefaultFtpsSessionFactory sf = new DefaultFtpsSessionFactory() {

        @Override
        protected FTPSClient createClientInstance() {
            return new SharedSSLFTPSClient();
        }

    };
    sf.setHost("...");
    sf.setPort(21);
    sf.setUsername("...");
    sf.setPassword("...");
    sf.setNeedClientAuth(true);
    return sf;
}

private static final class SharedSSLFTPSClient extends FTPSClient {

    @Override
    protected void _prepareDataSocket_(final Socket socket) throws IOException {
        if (socket instanceof SSLSocket) {
            // Control socket is SSL
            final SSLSession session = ((SSLSocket) _socket_).getSession();
            final SSLSessionContext context = session.getSessionContext();
            context.setSessionCacheSize(0); // you might want to limit the cache
            try {
                final Field sessionHostPortCache = context.getClass()
                        .getDeclaredField("sessionHostPortCache");
                sessionHostPortCache.setAccessible(true);
                final Object cache = sessionHostPortCache.get(context);
                final Method method = cache.getClass().getDeclaredMethod("put", Object.class,
                        Object.class);
                method.setAccessible(true);
                String key = String.format("%s:%s", socket.getInetAddress().getHostName(),
                        String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
                method.invoke(cache, key, session);
                key = String.format("%s:%s", socket.getInetAddress().getHostAddress(),
                        String.valueOf(socket.getPort())).toLowerCase(Locale.ROOT);
                method.invoke(cache, key, session);
            }
            catch (NoSuchFieldException e) {
                // Not running in expected JRE
                logger.warn("No field sessionHostPortCache in SSLSessionContext", e);
            }
            catch (Exception e) {
                // Not running in expected JRE
                logger.warn(e.getMessage());
            }
        }

    }

}