专栏首页与神兽党一起成长封装一个FTP工具类

封装一个FTP工具类

封装一个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> {
    @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类。目前为止我引入了一点点关于 业务的代码,但还没有耦合进来业务实现逻辑。

FTP工具类

其实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类。

回调操作

程序调用图

关于单元测试

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

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

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

代码地址

https://github.com/Honwhy/com... 见master分支

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 使用echarts问题1

    如果如果你是这样使用echarts的,那请注意了。 刚开始我用页面的DOM去做echarts.init(dom);

    用户3579639
  • 生成guid 的办法记录

    用户3579639
  • 我的解决stackoverflow无法正常登陆问题过程

    1.从stackoverflow.com中点击login链接,查看network情况,可以发现几处错误。

    用户3579639
  • 动手学深度学习(二) Softmax与分类模型

    softmax运算符(softmax operator)解决了以上两个问题。它通过下式将输出值变换成值为正且和为1的概率分布:

    致Great
  • ResNet可能是白痴?DeepMind给神经网络们集体测智商

    【新智元导读】DeepMind提出了一种让神经网络进行抽象推理的新方法,类似人类的IQ测试。结果发现经典模型如ResNet得分极低,数据稍有改动就变“白痴”,而...

    新智元
  • 天啊,你要的智商已下线——用我们的IQ测试题研究测量神经网络的抽象推理能力

    为了解决这个问题,我们设计了一种用于抽象推理的新颖结构,当训练数据和测试数据不同时,我们发现该模型能够精通某些特定形式的泛化,但在其他方面能力较弱。进一步地,当...

    用户1737318
  • 【RL-TCPnet网络教程】第37章 RL-TCPnet之FTP客户端

    本章节为大家讲解RL-TCPnet的FTP客户端应用,学习本章节前,务必要优先学习第35章的FTP基础知识。有了这些基础知识之后,再搞本章节会有事半功倍的效果。

    armfly
  • 服务网关和 Zuul0 简介1 实践

    网关是具体核心业务服务的看门神,相比具体实现业务的系统服务来说它是一个边缘服务,主要提供动态路由,监控,弹性,安全性等功能,下面我们从单体应用到多体应用的演化过...

    JavaEdge
  • where is count server execution interval configured

    在Chrome network tab里能观察到周期性的Odata call:

    Jerry Wang
  • 别找了,你最需要的公众号运营工具都在这!

    除了功能齐全,学以致用很重要。为大家推荐一些实用的微信运营工具,例如数据分析工具、排版、图片、H5页面、二维码、等相关工具,都经过亲测使用。

    用户6102055

扫码关注云+社区

领取腾讯云代金券

玩转腾讯云 有奖征文活动