前人的代碼中把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分支