前人的代码中把FTP操做和业务逻辑实现耦合在一块儿,听说通过屡次的修改,在性能表现方面已经很是靠谱。
在原来的代码中能够看到使用了commons-net进行FTP操做,使用commons-pool对象池方式管理FTP链接,
完成了多线程下载和上传的功能,本次的修改只是把耦合的地方剥离开来。git
使用apache commons pool对象池管理方式须要提供一个工厂类,管理对象的生成销毁等。
须要实现以下方法,github
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了,因此咱们的工厂类是这样的,apache
public class FtpClientFactory implements KeyedPooledObjectFactory<FtpClientConfig, FTPClient>{ ... }
相应的,咱们提供一个对象池的实现,其实很简单多线程
public class FtpClientPool extends GenericKeyedObjectPool<FtpClientConfig, FTPClient> { public FtpClientPool(FtpClientFactory factory, FtpPoolConfig config) { super(factory, config); } }
构造方法的参数就是咱们提供的对象工厂FtpClientFactory
和FtpPoolConfig
,FtpPoolConfig
是ide
public class FtpPoolConfig extends GenericKeyedObjectPoolConfig{ public FtpPoolConfig() { setTestWhileIdle(true); setTimeBetweenEvictionRunsMillis(60000); setMinEvictableIdleTimeMillis(1800000L); setTestOnBorrow(true); } }
针对FTP链接设置了一些参数。函数
外部只要拿到FtpClientPool
对象就能够获取FTPClient
对象、返回FTPClien
对象了,FTPClient
的生成销毁就交给了FtpClientFactory
管理。工具
FTP链接池比方数据库链接池来看,使用链接池彷佛能够模仿Spring
的JdbcTemplate
,这个模板封装了
获取链接,执行数据库操做,返还链接给链接池的过程,在这里一样也适合。这里引入一个本身实现的"模板类",性能
public class FtpTemplate implements FtpOperations<K> { @Autowired private FtpClientPool ftpClientPool; }
继承本身定义的FTP操做接口,而且注入上一步封装好的对象池,固然在实践过程当中可能作不到像JdbcTemplate
那样彻底的泛型化。
好比为了泛型实例化,引入InterfaceConfig
类咱们才能真正实现FtpTemplate
类。单元测试
public class FtpTemplate implements FtpOperations<InterfaceConfig> { ... @Override public 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 pool if(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类。目前为止我引入了一点点关于
业务的代码,但尚未耦合进来业务实现逻辑。
其实FtpTemplate
已是一个适合业务逻辑实现的工具类的,可是它的功能单纯一些,为了完成特殊的业务功能,
如多线程下载,下载文件业务处理成功后才删除远端服务的文件等,这里再对FtpTemplate
作一次封装。
public class FtpUtils { @Autowired private 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类。
略
从上往下能够看出来三处封装,分别是FtpUtils
、FtpTemplate
和FtpClientPool
,咱们能够分别
对他们进行单元测试,
FtpClientPool
,测试FTP链接问题等FtpTemplate
,测试FTP操做问题等FtpUtils
,传入回调函数,测试业务问题因为JUnit
对多线程单元测试并无提供支持,因此第3点实现起来有困难。
https://github.com/Honwhy/com... 见master分支