This repository has been archived by the owner. It is now read-only.
ftp connection pool
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.settings
src
.gitignore
LICENSE
README.md
pom.xml

README.md

使用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