使用commons-pool管理FTP连接
背景
在文章,已经完成一版对FTP连接的管理,设计了模板方法,为工具类上传和下载文件方法的提供获取对象和释放对象支持。
此番重新造轮子更多地考虑功能复用的角度,支持更多可配置参数,不止是连接池相关的属性;只考虑维护同一个连接请求多个连接对象的情况,将多个不同请求的情况交给外部管理,由外部定制,类似多数据源数据库连接的方式;重新审视模板方法的使用,在不引入模板的方法,设计封装对象池管理功能,以更自然的方式获取对象和释放对象。
思路
整体的思路来自BasicDataSource
,它是javax.sql.DataSource
的具体实现,实现的是数据库连接池,使用上完全感觉不到对象池的存在,通过dataSource
获取对象connection
,释放对象则使用connection.close()
即可。然而,与javax.sql.DataSource
和java.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
或许是这样的,
、
关于对象池的属性的说明请参考更多网络文章,或者官方文档。
获取对象
这是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属性的协议分别创建不同的对象工厂,如FTPClientFactory
,FTPSClientFactory
等。
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(_connectionFactory, config); _connectionFactory.setPool(_pool);//反向引用}
此外,在执行PoolableConnectionFactory
的makeObject
方法,对生成的对象做一次封装,传递PoolableConnectionFactory
持有的对象池给新生成的的对象。
public PooledObjectmakeObject() throws Exception { FTPClient ftpClient = factory.getFTPClient(); FTPClientWrapperConnection wrapperConnection = new FTPClientWrapperConnection(ftpClient,pool); return new DefaultPooledObject (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工具相比还缺少上传下载等功能的封装,而这些功能将会交给另外的工程来完成。
项目地址: