使用commons-pool管理FTP连接

使用commons-pool管理FTP连接

背景

封装一个FTP工具类文章,已经完成一版对FTP连接的管理,设计了模板方法,为工具类上传和下载文件方法的提供获取对象和释放对象支持。

此番重新造轮子更多地考虑功能复用的角度,支持更多可配置参数,不止是连接池相关的属性;只考虑维护同一个连接请求多个连接对象的情况,将多个不同请求的情况交给外部管理,由外部定制,类似多数据源数据库连接的方式;重新审视模板方法的使用,在不引入模板的方法,设计封装对象池管理功能,以更自然的方式获取对象和释放对象。

思路

整体的思路来自BasicDataSource,它是javax.sql.DataSource的具体实现,实现的是数据库连接池,使用上完全感觉不到对象池的存在,通过dataSource获取对象connection,释放对象则使用connection.close()即可。然而,与javax.sql.DataSourcejava.sql.Connection不同的是,JDK中并没有支持FTP协议的类似的框架;另一个问题则是,项目中已经使用commons-net来建立FTP连接,使用FTPClient等API了,如何将具体实现整合到要新定义的接口中,似乎是本末倒置的。

实现

整体框架

首先定义整体框架,类似DataSource

public interface FTPManager extends AutoCloseable {

    FTPConnection getFTPConnection() throws FTPException ;
}

定义连接对象,

public interface FTPConnection extends Wrapper, AutoCloseable {

    void close() throws FTPException;
    
    boolean isClosed() throws FTPException;
    
    //ftp|ftps|ftp:http -- subprotocol
    //String getSchema() throws FTPException;
}

从这个框架出发,获取连接对象使用ftpManager.getFTPConnection,释放对象使用ftpConnection.close

整理配置属性

引入主角FTPCPManager,在FTPCPManager定义和连接相关的属性,抽取一个父类PoolProperties专门用于配置对象池相关的配置。

public class FTPCPManager extends PoolProperties implements FTPManager {
    protected String url;    
    protected String username;
    protected String password;
    protected String proxyHost = null;
    protected int proxyPort = 80;
    protected String proxyUser = null;
    protected String proxyPassword = null;
    protected String encoding = StandardCharsets.UTF_8.name();
    protected long keepAliveTimeout = -1;
    protected int controlKeepAliveReplyTimeout = -1;
    protected String serverTimeZoneId = null;
    protected int bufferSize = -1;
    protected int connectTimeout = -1;
    protected String localActive = "false";
}

类似地,若使用Spring的xml配置,配置FTPCPManager或许是这样的,

<bean id="ftpCPManager" class="com.honey.ftpcp.FTPCPManager" destroy-method="close">  
    <property name="url" value="ftp://127.0.0.1"/>  
    <property name="username" value="sa"/>  
    <property name="password" value="sa"/>
    <property name="maxTotal" value="100"/>
    <property name="maxIdle" value="8"/> 
    <property name="minIdle" value="0"/>
    <property name="maxWait" value="1000"/>
    <property name="initialSize" value="2"/>
    <property name="testOnBorrow" value="true"/>、
    <property name="testOnReturn" value="false"/>
    <property name="testWhileIdle" value="false"/>
</bean>

关于对象池的属性的说明请参考更多网络文章,或者官方文档。

获取对象

这是FTPCPManager最核心的部分了,入口是getFTPConnection方法,

public FTPConnection getFTPConnection() throws FTPException {
    return createFTPManager().getFTPConnection();
}
protected synchronized FTPManager createFTPManager() {
    if(ftpManager != null) {
        return ftpManager;
    }
    //create connection factory
    IFTPClientFactory ftpClientFactory = createFTPClientFactory();
    PoolingFTPManager newManager = new PoolingFTPManager(ftpClientFactory, this);
    connectionPool = newManager.getPool();
    this.ftpManager = newManager;
    return newManager;
}

FTPCPManager做了一个特殊处理,在内部维护了新的FTPManager类型变量,不同的是它带有对象池管理的功能,它存在的意义就是将对象池和对象工厂组合起来,这样的处理方式减轻了FTPCPManager的负担,职责更少,只提供重要接口,重要的实现还是交给被代理的成员。(当然,这里也可以有不同的看法)。createFTPClientFactory会根据url属性的协议分别创建不同的对象工厂,如FTPClientFactoryFTPSClientFactory等。

PoolingFTPManager的构造方法,需要对象工厂及连接池配置属性两个参数,FTPCPManager正好继承扩展了PoolProperties类,作为连接池配置参数很合适。所以构造被代理的成员,即newManager = new PoolingFTPManager(ftpClientFactory, this)

构造好PoolingFTPManager的实例后,就可以获取FTPConnection连接对象了,接下来就是对象池的功能了。整体时序图如下,

对象的获取最终还是对象池与对象工厂的事情。

释放对象

为了让FTPConnection执行close方法的时候能够释放自己,将自己return到对象池,必须对FTPConnection做一些封装,连接对象需要记住最初的对象池对象,而对象池需要通过对象工厂来构造,通过这些条件代码的实现思路如下, 在构造PoolingFTPManager的同时也针对FTP对象工厂进行了封装,把原来的IFTPClientFactory封装成PoolableConnectionFactory类型,并且PoolableConnectionFactory持有GenericObjectPool类型的的对象池变量。在构造完GenericObjectPool对象池后,将对象池引用设置到PoolableConnectionFactory中。

PoolingFTPManager(IFTPClientFactory clientFactory, PoolProperties poolProperties) {
    //create object factory
    _connectionFactory = new PoolableConnectionFactory(clientFactory);
    GenericObjectPoolConfig config = new GenericObjectPoolConfig();
    // set config
    
    _pool = new GenericObjectPool<FTPConnection>(_connectionFactory, config);
    _connectionFactory.setPool(_pool);//反向引用
}

此外,在执行PoolableConnectionFactorymakeObject方法,对生成的对象做一次封装,传递PoolableConnectionFactory持有的对象池给新生成的的对象。

public PooledObject<FTPConnection> makeObject() throws Exception {
    FTPClient ftpClient = factory.getFTPClient();
    FTPClientWrapperConnection wrapperConnection = new FTPClientWrapperConnection(ftpClient,pool);
    return new DefaultPooledObject<FTPConnection>(wrapperConnection);
}

这个FTPClientWrapperConnection类就是关键了。FTPConnection执行close方法能将自己释放,return到对象池,就是由FTPClientWrapperConnection具体实现的。

public void close() throws FTPException {
    try {
        if(pool != null && !pool.isClosed()) {
            pool.returnObject(this);
        } else {
            if(ftpClient!=null) {
                ftpClient.logout();
                ftpClient.disconnect();
            }
        }
    } catch (Exception e) {
        //swallow everything
    } finally {
        _closed = true;
    }
}

简单测试

用一个测试来表现这个获取和释放对象的功能,

public class FTPCPManagerTest {

    @Test
    public void test1() throws Exception {
        FTPCPManager manager = new FTPCPManager();
        manager.setUrl("ftp://127.0.0.1");
        manager.setUsername("sa");
        manager.setPassword("sa");
        manager.setInitialSize(2);
        manager.setKeepAliveTimeout(1 * 60);
        
        FTPConnection conn = manager.getFTPConnection();
        assertTrue(manager.getNumActive() == 1);
        assertTrue(manager.getNumIdle() == 1);
        conn.close();
        assertTrue(manager.getNumActive() == 0);
        assertTrue(manager.getNumIdle() == 2);
        manager.close();
    }
}

首先initialSize设置了对象池初始大小,在构造对象池的时候就调用了两次对象工厂的makeObject方法生成两个对象。然后是通过manager获取一次对象,此时检测对象池的被借出的对象manager.getNumActive() == 1是否成立,检测对象池保留的对象manager.getNumIdle() == 1是否成立。接下里是调用连接对象的close方法,再次检测比较对象池保留的对象是否manager.getNumIdle() == 2。如果以上断言都成立,证明对象的获取和释放使用到了对象池管理而且能够正常运行。

总结

至此,使用commons-pool管理FTP连接的功能算基本完成了。与封装一个FTP工具类文章中的FTP工具相比还缺少上传下载等功能的封装,而这些功能将会交给另外的工程来完成。 项目地址:https://github.com/Honwhy/ftpcp

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏陈树义

从字节码层面,解析 Java 布尔型的实现原理

最近在系统回顾学习 Java 虚拟机方面的知识,其中想到一个很有意思的问题:布尔型在虚拟机中到底是什么类型?

15820
来自专栏栗霖积跬步之旅

java多线程编程核心技术——第二章总结

第一节synchronized同步方法目录 1.1方法内的变量为线程安全的 1.2实例变量非线程安全 1.3多个对象多个锁 1.4synch...

218100
来自专栏java一日一条

JAVA中volatile、synchronized和lock解析

在研究并发程序时,我们需要了解java中关键字volatile和synchronized关键字的使用以及lock类的用法。

13520
来自专栏一个会写诗的程序员的博客

JVM、Java编译器和Java解释器

java解释器就是把在java虚拟机上运行的目标代码(字节码)解释成为具体平台的机器码的程序。即jdk或jre目录下bin目录中的java.exe文件,而jav...

95920
来自专栏我是攻城师

理解Java里面的代理模式

代理模式是23种设计模式中非常经典的一种模式,在日常生活中到处充满了代理模式的痕迹,常见的比如火车代售点买票,各种公共服务大厅,以及各种网上购物平台其实都可以看...

44110
来自专栏IT派

爬虫工程师面试题总结,带你入门Python爬虫

46130
来自专栏Java架构师历程

JVM加载class文件的原理

当Java编译器编译好.class文件之后,我们需要使用JVM来运行这个class文件。那么最开始的工作就是要把字节码从磁盘输入到内存中,这个过程我们叫做【加载...

54220
来自专栏WindCoder

Java基础小结(三)

以上这些类是传统遗留的,在Java2中引入了一种新的框架-集合框架(Collection)

7110
来自专栏Felix的技术分享

理解对C++裸指针释放后重用的问题

24590
来自专栏java学习

1.3java的运行原理

java的运行原理 这里我们简单分析一下我们的第一个应用程序,其中涉及到很多没有接触过的概念,大家可先阅读以下,以后会详细讲解。重点是理解java的运行原理。 ...

38740

扫码关注云+社区

领取腾讯云代金券