自定义 ftpClient / ftp client 连接池

项目中要用到ftp上传文件, 但可能会出现很多人批量上传, 所以就想着弄一个client的池子,在网上搜索了一番,自己改进了一番
总共需要三个类:

  • 一个配置类 FTPClientConfigure. 是ftp的连接信息
  • 一个工厂类 FtpClientFactory 通过配置信息创建client对象,以及销毁,验证对象
  • 一个poo类 FtpClientPool 工厂创建的client对象都存在pool里

具体如下

1.创建配置类

public class FTPClientConfigure {
  private String host;
  private int port;
  private String username;
  private String password;
  private String passiveMode;
  private String encoding;
  private int clientTimeout;
  private int threadNum;
  private int transferFileType;
  private boolean renameUploaded;
  private int retryTimes;
  private String path;
// get set 方法省略

2.创建工厂类

package ....;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;

/**
 * author: 
 * create: 2019-04-29 11:19
 * description:
 **/
public class FtpClientFactory extends BasePooledObjectFactory<FTPClient> {

    private static Logger logger = LoggerFactory.getLogger(FtpClientFactory.class);
    private FTPClientConfigure config;

    public FtpClientFactory(FTPClientConfigure config) {
        this.config = config;
    }

    /**
     * 创建FtpClient对象
     */
    @Override
    public FTPClient create() {
        FTPClient ftpClient = new FTPClient();
        ftpClient.setControlEncoding(config.getEncoding());
        ftpClient.setConnectTimeout(config.getClientTimeout());
        try {
            ftpClient.connect(config.getHost(), config.getPort());
            int replyCode = ftpClient.getReplyCode();
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                ftpClient.disconnect();
                logger.warn("服务器拒绝连接,检查ip和端口是否正确");
                return null;
            }
            boolean login = ftpClient.login(config.getUsername(), config.getPassword());
            if (!login) {
                throw new RuntimeException("登陆ftp文件系统失败! userName:" + config.getUsername() + " ; password:" + config.getPassword());
            }
            ftpClient.setFileType(config.getTransferFileType());
        } catch (IOException e) {
            logger.error("创建ftp连接失败...", e);
        }
        return ftpClient;
    }

    /**
     * 用PooledObject封装对象放入池中
     */
    @Override
    public PooledObject<FTPClient> wrap(FTPClient ftpClient) {
        return new DefaultPooledObject<>(ftpClient);
    }

    /**
     * 销毁FtpClient对象
     */
    @Override
    public void destroyObject(PooledObject<FTPClient> ftpPooled) {
        if (ftpPooled == null) {
            return;
        }
        FTPClient ftpClient = ftpPooled.getObject();
        destroyObject(ftpClient);
    }

    public void destroyObject(FTPClient ftpClient) {
        if (ftpClient == null) {
            return;
        }
        try {
            if (ftpClient.isConnected()) {
                ftpClient.logout();
            }
        } catch (IOException io) {
            logger.error("ftp client 退出失败", io);
        } finally {
            try {
                ftpClient.disconnect();
            } catch (IOException io) {
                logger.error("ftp client 关闭失败", io);
            }
        }
    }



    /**
     * 验证FtpClient对象
     */
    @Override
    public boolean validateObject(PooledObject<FTPClient> ftpPooled) {
        FTPClient ftpClient = ftpPooled.getObject();
        return validateObject(ftpClient);
    }

    public boolean validateObject(FTPClient ftpClient) {
        try {
            return ftpClient.sendNoOp();
        } catch (IOException e) {
            logger.error("验证fpt client 失败", e);
        }
        return false;
    }
}

3.创建池子pool

package ...r;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.BaseObjectPool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * author: 
 * create: 2019-04-29 10:05
 * description: ...
 **/
public class FtpClientPool extends BaseObjectPool<FTPClient> {

    private static final FtpClientPool ftpClientPool = new FtpClientPool(new FtpClientFactory(new FTPClientConfigure()));

    private static final int IDLE_POOL_SIZE = 5;
    private static final int MAX_CLIENT_SIZE = 20;
    private int allClient = 0;
    private final BlockingQueue<FTPClient> pool;
    private final FtpClientFactory factory;


    public static FtpClientPool getInstance(){
        return ftpClientPool;
    }
    
     /**
     * 初始化连接池,需要注入一个工厂来提供FTPClient实例
     * @param idleSize
     */
    private FtpClientPool(FtpClientFactory factory){
        this(IDLE_POOL_SIZE, factory);
    }

    /**
     * 初始化连接池,需要注入一个工厂来提供FTPClient,和初始化连接池的大小
     *
     * @param idleSize
     * @param factory
     */
    private FtpClientPool(int idleSize, FtpClientFactory factory) {
        this.factory = factory;
        pool = new ArrayBlockingQueue<>(idleSize);
        initPool(idleSize);
    }

   
    private void initPool(int idleSize)  {
        for (int i = 0; i < idleSize; i++) {
            //插入对象到队列,offer为非阻塞,如果等待3秒还是满的,则放弃加入
            try {
                pool.offer(factory.create(), 3, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            allClient++;
        }

    }

	 /**
     * 
     * 借出对象,就是获取client
     */
    @Override
    public FTPClient borrowObject()  {
        // 非阻塞获取(具体可查询BlockingQueue的使用方式)
        FTPClient client = pool.poll();
        // 如果pool中没有client
        if (client == null) {
            //已存在的client没有超过最大数量,创建.
            if (allClient < MAX_CLIENT_SIZE) {
                client = factory.create();
                allClient++;
            } else {
                // 已存在的client超过了最大数量,当前线程阻塞获取,一直等到拿到client.
                try {
                    client = pool.take();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        } else if (!factory.validateObject(client)) {
            // 获取到了client,但验证不通过,从池中移除
            invalidateObject(client);
            //制造新对象
            client = factory.create();
        }
        return client;
    }

    /**
     * 放回到池中
     * @param client
     */
    @Override
    public void returnObject(FTPClient client)  {
        if (null == client) {
            return;
        }
        // 放入池中
        boolean put = false;
        try {
            put = pool.offer(client, 3, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (!put) {
            // 池中已满,断开连接,销毁
            try {
                factory.destroyObject(client);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        allClient--;

    }

    /**
     * 作废
     * @param client
     */
    @Override
    public void invalidateObject(FTPClient client){
        //移除无效的客户端
        pool.remove(client);
    }
}

这样就可以用了

 // 初始化pool
 FtpClientPool pool = FtpClientPool.getInstance();
 // 获取client
 FTPClient ftpClient = pool.borrowObject();
 // ......自己的业务逻辑,包括切换目录,上传,下载......
 
 // 返回client
 pool.returnObject(ftpClient);

最后注意,我是写好代码后发到的这里,还没在项目中使用, 可能存在bug,但也可能没有bug

<think>好的,我现在要帮用户解决如何使用Spring Boot连接FTP服务器的问题。首先,我需要理清用户的需求。用户希望有一个示例教程,说明在Spring Boot项目中如何连接FTP服务器,可能包括配置和代码示例。 首先,我需要回忆一下Spring Boot整合FTP的常见方法。通常,这涉及到使用FTP客户端库,比如Apache Commons Net的FTPClient。用户可能还需要连接池管理,以提高性能,特别是在频繁上传下载文件的情况下。 接下来,我需要查看用户提供的引用内容。引用[1]、[2]、[3]提到了FTP连接池的配置,引用[4]和[5]提供了测试连接和上传文件的示例代码。这说明用户可能对连接池的实现和具体的使用方法感兴趣。 用户的问题是如何使用Spring Boot连接FTP,所以步骤应该包括添加依赖、配置参数、创建FTP工具类、配置连接池以及测试。需要确保每一步都清晰,代码示例正确,并且符合Spring Boot的约定,比如使用application.properties进行配置。 需要注意的地方是,不同的引用中可能有不同的实现方式,比如有些使用@Autowired注入,有些是静态工具类。需要统一这些方法,选择一种常见且易于理解的方式展示给用户。例如,可以创建一个FtpConfig配置类来读取配置,然后生成FTPClientFactory和连接池。 另外,用户可能对连接池的配置参数不太熟悉,需要解释各个参数的作用,比如maxTotal、maxIdle等,这样用户可以根据自己的需求调整。 在代码示例部分,要确保语法正确,比如在FtpUtil类中,使用连接池获取和归还连接,处理异常情况。上传文件的示例代码需要包括如何从连接池获取客户端,切换工作目录,上传文件,然后归还连接。 测试部分,用户可能想知道如何验证连接是否成功,所以需要提供一个简单的测试方法,比如检查FTP服务器是否可达,或者上传一个测试文件。 最后,生成的相关问题需要围绕用户可能的后续疑问,比如配置文件加密、断点续传、异常处理等,这些都是在实际应用中常见的问题。 总结一下,我需要按照以下步骤组织回答: 1. 添加依赖:Apache Commons Net。 2. 配置FTP参数:application.properties中设置主机、端口、用户名、密码等。 3. 创建FTP配置类:读取配置,创建连接工厂连接池。 4. 编写工具类:实现连接获取、文件上传等方法。 5. 测试连接:编写测试用例检查连接状态和上传功能。 6. 生成相关问题,帮助用户深入理解。 同时,要确保所有代码示例正确,引用合适的参考文献,比如连接池的配置参考引用[1]和[2],测试部分参考引用[4]和[5]。 现在需要检查是否有遗漏的步骤,比如异常处理、连接池的具体实现细节是否需要进一步说明,或者是否需要提到Passive模式等FTP连接模式的区别。根据用户提供的引用,可能不需要深入这些细节,保持示例简洁即可。 最后,确保回答使用中文,数学公式和代码格式正确,结构清晰,分步骤说明,让用户能够逐步跟随操作。</think>### 使用Spring Boot连接FTP服务器的步骤说明 以下是基于Spring Boot整合FTP服务器的详细实现方法,结合连接池优化和实际代码示例: --- #### 1. **添加依赖** 在`pom.xml`中添加Apache Commons Net依赖以支持FTP操作: ```xml <dependency> <groupId>commons-net</groupId> <artifactId>commons-net</artifactId> <version>3.9.0</version> </dependency> ``` --- #### 2. **配置FTP参数** 在`application.properties`中配置FTP服务器信息: ```properties # FTP基础配置 ftp.host=192.168.1.xxx ftp.port=21 ftp.username=admin ftp.password=admin@123 ftp.pool.max-total=8 # 连接池最大连接ftp.pool.max-idle=4 # 最大空闲连接ftp.pool.min-idle=2 # 最小空闲连接数 ``` --- #### 3. **创建FTP连接池配置类** 通过`@Configuration`和`@Bean`定义连接池相关组件: ```java @Configuration public class FtpConfig { @Value("${ftp.host}") private String host; @Value("${ftp.port}") private int port; @Value("${ftp.username}") private String username; @Value("${ftp.password}") private String password; // 创建FTP客户端工厂 @Bean public FTPClientFactory ftpClientFactory() { return new FTPClientFactory(host, port, username, password); } // 初始化连接池 @Bean public GenericObjectPool<FTPClient> ftpClientPool( @Autowired FTPClientFactory factory) { return new GenericObjectPool<>(factory, createPoolConfig()); } // 配置连接池参数 private GenericObjectPoolConfig<FTPClient> createPoolConfig() { GenericObjectPoolConfig<FTPClient> config = new GenericObjectPoolConfig<>(); config.setMaxTotal(8); config.setMaxIdle(4); config.setMinIdle(2); return config; } } ``` --- #### 4. **编写FTP工具类** 实现文件上传、下载等操作的工具类: ```java @Component public class FtpUtil { @Autowired private GenericObjectPool<FTPClient> ftpClientPool; // 获取FTP连接 private FTPClient borrowClient() throws Exception { FTPClient client = ftpClientPool.borrowObject(); client.enterLocalPassiveMode(); // 设置为被动模式[^5] return client; } // 上传文件 public boolean uploadFile(String remotePath, File file) { try (FTPClient client = borrowClient()) { client.changeWorkingDirectory(remotePath); client.storeFile(file.getName(), new FileInputStream(file)); return true; } catch (Exception e) { e.printStackTrace(); return false; } finally { ftpClientPool.returnObject(client); // 归还连接到池中[^2] } } } ``` --- #### 5. **测试FTP连接** 编写测试类验证连接和上传功能: ```java @SpringBootTest public class FtpTest { @Autowired private FtpUtil ftpUtil; @Test void testUpload() { File file = new File("test.txt"); boolean success = ftpUtil.uploadFile("/uploads/", file); assertTrue(success); } // 检查服务器状态[^4] @Test void testServerStatus() { assertDoesNotThrow(() -> ftpUtil.borrowClient()); } } ``` --- ### **关键优化点** 1. **连接池管理**:通过`GenericObjectPool`复用连接,减少频繁创建的开销[^1][^3]。 2. **被动模式**:解决防火墙限制问题,提升文件传输稳定性。 3. **异常处理**:在`finally`块中确保连接归还,避免资源泄漏。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值