apache-commons commons-pool2 integrates commons-net custom FTPClient object pool

Posted by slionheart on Fri, 13 Sep 2019 11:12:23 +0200

Introduction to commons-net package

commons-net is Apache commons tool kit for network. It implements some common network tools, such as smtp, pop3, telnet, ftp, udp and so on. This paper mainly uses its FTP tool.

Problems with FTP Tools

When using the ftp tool provided by commons-net, it is found that the complete connection and login process must be made once at a time. Every time a connection is created, it will soon run out of connections. If you use singletons, it will be inefficient, because there is only one connection, so consider using object pooling.

I've done this before, but there are a lot of things to consider when defining object pools myself. Like thread pools, you need to consider the number of core objects, the maximum number of objects, when to create objects, and queues, so you can use apache's commons-pool2 to make an object pool.

How to use commons-pool2

It can be said that if I want to make an object pool tool for others to use, the first thing to consider is what is in the pool, how users create objects in the pool, and then provide methods to borrow and recycle objects. I can abstract objects in the pool, which are managed by a factory, and users'objects are from me. Common objects inherit or implement, or aggregate.

In fact, spring-data-redis has given us a complete example of using commons-pool2, which is commons-pool2. Its pool object is redis connection, which you can see if you are interested.

Define Extended Objects for Objects FTPClient in the Pool

Because FTPClient's function is too simple to create a multi-tier directory by itself, it's necessary to wrap it up. Here you can expand the commonly used methods.

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;

@Slf4j
public class FtpClientExtend {
    private FTPClient ftpClient ;

    public FtpClientExtend(FTPClient ftpClient) {
        this.ftpClient = ftpClient;
    }

    /**
     * List files
     * @param filePath
     * @return
     * @throws IOException
     */
    public FTPFile[] listFiles(String filePath) throws IOException {
        return ftpClient.listFiles(filePath);
    }

    /**
     * Download File
     * @param filePath
     * @return
     */
    public InputStream downloadFile(String filePath) throws IOException {
        return ftpClient.retrieveFileStream(filePath);
    }

    /**
     * Storage file
     * @param s
     * @param inputStream
     */
    public void uploadFile(String filePath, InputStream inputStream) throws IOException {
        File targetFilePath = new File(filePath);
        Path path = targetFilePath.getParentFile().toPath();
        Iterator<Path> iterator = path.iterator();
        StringBuffer root = new StringBuffer("");
        while (iterator.hasNext()){
            Path next = iterator.next();
            root.append("/").append(next);

            //Try to cut into the catalog
            boolean success = ftpClient.changeWorkingDirectory(root.toString());
            if(!success){
                int mkd = ftpClient.mkd(next.toString());
                ftpClient.changeWorkingDirectory(root.toString());
            }
        }

        ftpClient.enterLocalPassiveMode();
        ftpClient.setControlEncoding("UTF-8");
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        boolean storeFileResult = ftpClient.storeFile(targetFilePath.getName(), inputStream);
        if (storeFileResult) {
            log.debug("Upload files:" + filePath + ",To directory:" + ftpClient.printWorkingDirectory() + " Success");
        }else{
            log.debug("Upload files:" + filePath + ",To directory:" + ftpClient.printWorkingDirectory() + " fail");
        }
    }
}

Using objects in aggregated package pools

The class of the wrapped object is the class that the factory actually generates in the pool. The diagram is given at the end of the article.

import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.commons.pool2.impl.GenericObjectPoolConfig;

public class FtpClientPool extends GenericObjectPool<FtpClientExtend> {

    public FtpClientPool(PooledObjectFactory<FtpClientExtend> factory) {
        super(factory);
    }

    public FtpClientPool(PooledObjectFactory<FtpClientExtend> factory, GenericObjectPoolConfig config) {
        super(factory, config);
    }
}

Establishing a factory for creating objects

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;

public class FtpClientFactory extends BasePooledObjectFactory<FtpClientExtend> {
    @Value("${ftp.host:localhost}")
    private String host;
    @Value("${ftp.port:21}")
    private int port;
    @Value("${ftp.username:ftpadmin}")
    private String username;
    @Value("${ftp.password:salt202}")
    private String password;

    @Override
    public FtpClientExtend create() throws Exception {
        FTPClient ftpClient = new FTPClient();
        ftpClient.connect(host,port);
        boolean login = ftpClient.login(username, password);
        if(!login){
            throw new RuntimeException("ftp Login failed,Check that the username password is correct["+host+":"+port+"]["+username+"]["+password+"]");
        }
        return new FtpClientExtend(ftpClient);
    }

    @Override
    public PooledObject<FtpClientExtend> wrap(FtpClientExtend ftpClientExtend) {
        return new DefaultPooledObject(ftpClientExtend);
    }
}

Usage method

@Autowired
private FtpClientPool ftpClientPool;

public void method(){
    FtpClientExtend ftpClientExtend = null;
    try{
        ftpClientExtend = ftpClientPool.borrowObject();
    }finally{
         if(ftpClientExtend != null) {
          ftpClientPool.returnObject(ftpClientExtend);
        }
    }
}

Schematic diagram

Topics: Java ftp Apache network Redis