封装一个FTP操作工具类

概述

前人的代码中把FTP操作和业务逻辑实现耦合在一起,据说经过多次的修改,在性能表现方面已经非常靠谱。
在原来的代码中可以看到使用了commons-net进行FTP操作,使用commons-pool对象池方式管理FTP连接,
完成了多线程下载和上传的功能,本次的修改只是把耦合的地方剥离开来。

FTP连接对象池

使用apache commons pool对象池管理方式需要提供一个工厂类,管理对象的生成销毁等。
需要实现如下方法,

PooledObject<V> makeObject(K key) throws Exception;
void destroyObject(K key, PooledObject<V> p) throws Exception;
boolean validateObject(K key, PooledObject<V> p);

apache commons pool提供了一个带泛型的接口KeyedPooledObjectFactory<K,V>
需要继承实现类提供对象工厂的key类型,及要生产的对象类型,key可以是一个类,包含FTP的IP
,端口,用户名密码等属性组成,目的是区分不同的FTP连接,

public class FtpClientConfig {private String host;private int port;private String username;private String password;
...
}

这里要生产的对象类型当然是FTPClient了,所以我们的工厂类是这样的,

public class FtpClientFactory implements KeyedPooledObjectFactory<FtpClientConfig, FTPClient>{
...
}

相应的,我们提供一个对象池的实现,其实很简单

public class FtpClientPool extends GenericKeyedObjectPool<FtpClientConfig, FTPClient>  {public FtpClientPool(FtpClientFactory factory, FtpPoolConfig config) {super(factory, config);}}

构造方法的参数就是我们提供的对象工厂FtpClientFactoryFtpPoolConfigFtpPoolConfig

public class FtpPoolConfig extends GenericKeyedObjectPoolConfig{public FtpPoolConfig() {setTestWhileIdle(true);setTimeBetweenEvictionRunsMillis(60000);setMinEvictableIdleTimeMillis(1800000L);setTestOnBorrow(true);}
}

针对FTP连接设置了一些参数。

外部只要拿到FtpClientPool对象就可以获取FTPClient对象、返回FTPClien对象了,
FTPClient的生成销毁就交给了FtpClientFactory管理。

使用FTP连接对象池

FTP连接池比方数据库连接池来看,使用连接池似乎可以模仿SpringJdbcTemplate,这个模板封装了
获取连接,执行数据库操作,返还连接给连接池的过程,在这里同样也适合。这里引入一个自己实现的"模板类",

public class FtpTemplate implements FtpOperations<K> {@Autowiredprivate FtpClientPool ftpClientPool;
}

继承自己定义的FTP操作接口,并且注入上一步封装好的对象池,当然在实践过程中可能做不到像JdbcTemplate
那样完全的泛型化。
比如为了泛型实例化,引入InterfaceConfig类我们才能真正实现FtpTemplate类。

public class FtpTemplate implements FtpOperations<InterfaceConfig> {...@Overridepublic String getFile(InterfaceConfig k, String fileName) throws Exception {if (logger.isDebugEnabled()) {logger.debug("正在下载" + toFtpInfo(k) + "/" + fileName + "文件");}final FTPClient client = getFtpClient(getFtpClientPool(), k);boolean ret = changeDirectory(client,k);try {if(ret) {return performPerFile(client, fileName);}return null;} catch(Exception e) {logger.error("下载" + toFtpInfo(k) + "/" + fileName + "文件异常",e);throw e;} finally {//return to object poolif(client != null) {returnFtpClient(getFtpClientPool(), k, client);}}}
...
}

通过InterfaceConfig提供对象池识别的key获得我们需要的对象池里的对象FTPClient

    private FTPClient getFtpClient(FtpClientPool ftpClientPool, InterfaceConfig k) throws Exception {FtpClientConfig config = buildFtpClientConfig(k);FTPClient client = null;try {client = ftpClientPool.borrowObject(config);} catch (Exception e) {logger.error("获取FTPClient对象异常 " + toFtpInfo(k),e);throw e;}return client;}private FtpClientConfig buildFtpClientConfig(InterfaceConfig k) {FtpClientConfig config = new FtpClientConfig();config.setHost(k.getFtpUrl());config.setPort(Integer.valueOf(k.getFtpPort()));config.setUsername(k.getUserName());config.setPassword(k.getPwd());return config;}    

这个InterfaceConfig是业务代码中的对象类型,可能是Model类。目前为止我引入了一点点关于
业务的代码,但还没有耦合进来业务实现逻辑。

FTP工具类

其实FtpTemplate已经是一个适合业务逻辑实现的工具类的,但是它的功能单纯一些,为了完成特殊的业务功能,
如多线程下载,下载文件业务处理成功后才删除远端服务的文件等,这里再对FtpTemplate做一次封装。

public class FtpUtils {@Autowiredprivate FtpTemplate ftpTemplate;private ConcurrentHashMap<String, ThreadPoolExecutor> poolMap = new ConcurrentHashMap<>(); //存储线程池public void downloadDirectory(InterfaceConfig config,final FtpCallback<FtpFile,Boolean> callback) {logger.info("正在下载FTP目录" + toFtpInfo(config));ThreadPoolExecutor workPool = poolMap.get(config.getInterfaceCode());if(workPool == null) {BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(100);workPool = new ThreadPoolExecutor(MAX_CORE_NUM, MAX_THREAD_NUM, 1, TimeUnit.MINUTES, workQueue);poolMap.put(config.getInterfaceCode(), workPool);}try {List<String> fileNames = ftpTemplate.listFiles(config,config.getOrdersCount());BlockingQueue<String> fileQueue = new LinkedBlockingQueue<String>(fileNames); //生产者资料for(int i = 0; i < config.getThreadNum(); i++) {try {workPool.execute(new GetFileConsumer(config, fileQueue, callback));} catch (Exception e) {logger.error("提交线程出现异常",e);}}} catch (Exception e) {logger.error("FTP操作出现异常"+toFtpInfo(config),e);}logger.info("下载FTP目录完成" + toFtpInfo(config));}
}

注入了FtpTemplate,加入了多线程线程池管理,下载方法也需要外部传入回调方法。
回调方法中就可以完成保存下载的FTP文件,删除远端对应的文件等逻辑。即使了多了一层多线程
下载功能的封装,我们也没有把业务处理逻辑耦合进来。当然,不满意的地方还是引入了业务的Model类。

回调操作

程序调用图

关于单元测试

从上往下可以看出来三处封装,分别是FtpUtilsFtpTemplateFtpClientPool,我们可以分别
对他们进行单元测试,

  1. 注入FtpClientPool,测试FTP连接问题等
  2. 注入FtpTemplate,测试FTP操作问题等
  3. 注入FtpUtils,传入回调函数,测试业务问题

由于JUnit对多线程单元测试并没有提供支持,所以第3点实现起来有困难。

封装一个FTPClient连接池工具类相关推荐

  1. 封装一个常用的js工具类

    /*** @author:水痕* @timer:2016-07-28* @email:332904234@qq.com* @version:1.0* @title:封装一个自己常用的工具类js* @n ...

  2. 使用Python 封装一个简单的Mysql工具类

    pymysql操作mysql,虽然简单,但每次都要链接数据库,获取游标,关闭游标,关闭链接.这些操作无技术含量,还要重复编写!!想一想不如封装一个DBUtil,来提高开发效率. 要编写工具类首先要把公 ...

  3. Jedis连接池:JedisPool及连接池工具类搭建

    文章目录 Jedis连接池 连接池建立步骤 代码案例 JedisPoolUtils工具类 创建配置文件 编写工具类 编写测试代码 Jedis连接池 连接池建立步骤 JedisPool的配置参数大部分是 ...

  4. C3P0连接池工具类使用

    c3p0的基本连接配置文件 c3p0-config.xml <c3p0-config><default-config><property name="drive ...

  5. java ftpclient 池_Java FTPClient连接池的实现

    最近在写一个FTP上传工具,用到了Apache的FTPClient,为了提高上传效率,我采用了多线程的方式,但是每个线程频繁的创建和销毁FTPClient对象势必会造成不必要的开销,因此,此处最好使用 ...

  6. JDBC、封装JDBC连接池、第三方连接池工具

    主要内容: JDBC简介 JDBC来源 通过代码实现JDBC JDBC的改进需求 JDBC改进的代码实现 JDBC使用的设计模式 封装连接池 封装JDBC连接池 ThreadLoacl的使用 Thre ...

  7. android自定义线程池工具类,妈妈再也不用担心你不会使用线程池了(ThreadUtils)...

    为什么要用线程池 使用线程池管理线程有如下优点:降低资源消耗:通过重复利用已创建的线程降低线程创建和销毁造成的消耗. 提高响应速度:当任务到达时,任务可以不需要等到线程创建就能立即执行. 提高线程的可 ...

  8. python调用公共方法_common: 这是一个Python的公共工具类,集成了各种主要的python常用方法...

    common 介绍 这是一个Python的公共工具类,集成了各种主要的python常用方法. 本人是做java开发的,学习python很多情况下是把他当做一个脚本来使用,在使用的过程中,发现很多的功能 ...

  9. basicdatasourcefactory mysql_Java基础-DBCP连接池(BasicDataSource类)详解

    Java基础-DBCP连接池(BasicDataSource类)详解 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 实际开发中"获得连接"或"释放资源 ...

  10. 为什么要用Hibernate框架? 把SessionFactory,Session,Transcational封装成包含crud的工具类并且处理了事务,那不是用不着spring了?...

    既然用Hibernate框架访问管理持久层,那为何又提到用Spring来管理以及整合Hibernate呢?把SessionFactory,Session,Transcational封装成包含crud的 ...

最新文章

  1. C++ 笔记(28)— C++ 中 NULL和 nullptr 的区别
  2. 【c语言】hello
  3. stylus-loader (copy)
  4. hackme_Login As Admin 0
  5. 【译】UNIVERSAL IMAGE LOADER. PART 3---ImageLoader详解
  6. 8.16模拟:树上算法
  7. mllib协同过滤 java实现_协同过滤(ALS)算法介绍及Spark MLlib调用实例(Scala/Java/Python)...
  8. (转)淘淘商城系列——使用solrj来测试索引库
  9. 又增长了,微信及WeChat月活达12.25亿 !
  10. Mac底下java和jre路径
  11. javascript之js实现简单的无缝轮播图(可调节方向)
  12. 软考计算机网络初级试题答案,2015年下半年中级软考《计算机网络—网络工程师》试题及答案...
  13. 小米计算机视觉算法工程师面试题
  14. 深度学习 - 生成对抗网络
  15. ADCS relay
  16. 【MySQL连接】MySQLdb安装与使用
  17. 关于KEIL5最新版没有ATMEL(含89C51芯片)的情况
  18. 学校开展计算机教学的简报,教学工作简报(2019年第5期)
  19. oppor9桌面布局设置_oppor9添加桌面图标
  20. 协作通信-af df的matlab仿真,协作通信三种协作方式(AF+DF+CC)的matlab仿真程序

热门文章

  1. 18. Magento 细节
  2. java如果目录不存在则创建目录_java – 创建一个目录,如果它不存在,然后再创建该目录中的文件...
  3. typeof的用法 typeof的返回值是一个字符串 返回的字符串类别
  4. 两个可用于浏览器兼容性测试的Firefox插件
  5. angr进阶(2)C++程序的处理
  6. yii2 advanced版基础部分
  7. 女生真的不适合做程序么?。。我的成长之路。。
  8. 使用getopt_long解析程序长选项参数
  9. HTML标签margin和padding的默认属性值
  10. SQL 查找是否“存在“,别再 count 了,很耗费时间的!