最近在写一个FTP上传工具,用到了Apache的FTPClient,但是每个线程频繁的创建和销毁FTPClient对象对服务器的压力很大,因此,此处最好使用一个FTPClient连接池。仔细翻了一下Apache的api,发现它并没有一个FTPClientPool的实现,所以,不得不自己写一个FTPClientPool。下面就大体介绍一下开发连接池的整个过程,供大家参考。
我们可以利用Apache提供的common-pool包来协助我们开发连接池。而开发一个简单的对象池,仅需要实现common-pool 包中的ObjectPool和PoolableObjectFactory两个接口即可。
为了减少频繁创建、销毁对象带来的性能消耗,我们可以利用对象池的技术来实现对象的复用。对象池提供了一种机制,它可以管理对象池中对象的生命周期,提供了获取和释放对象的方法,可以让客户端很方便的使用对象池中的对象。
pom引入依赖创建ftp配置信息commons-net commons-net3.5 commons-pool commons-pool1.6 org.apache.commons commons-pool22.0
在resources目录下创建ftp.properties配置文件,目录结构如下:
image.png
添加如下的配置信息:
########### FTP用户名称 ###########ftp.userName=hrabbit########### FTP用户密码 ###########ftp.passWord=123456########### FTP主机IP ###########ftp.host=127.0.0.1########### FTP主机端口号 ###########ftp.port=21########### 保存根路径 ###########ftp.baseUrl=/创建FTPProperties.java配置文件
加载配置内容到Spring中,配置信息基本延用我的就可以。
@Data@Component@PropertySource("classpath:ftp.properties")@ConfigurationProperties(prefix = "ftp")public class FTPProperties { private String username; private String password; private String host; private Integer port; private String baseUrl; private Integer passiveMode = FTP.BINARY_FILE_TYPE; private String encoding="UTF-8"; private int clientTimeout=120000; private int bufferSize; private int transferFileType=FTP.BINARY_FILE_TYPE; private boolean renameUploaded; private int retryTime;
}创建FTPClientPool线程池@Slf4j@SuppressWarnings("all")public class FTPClientPool implements ObjectPool { private static final int DEFAULT_POOL_SIZE = 10; public BlockingQueue blockingQueue; private FTPClientFactory factory; public FTPClientPool(FTPClientFactory factory) throws Exception { this(DEFAULT_POOL_SIZE, factory);
} public FTPClientPool(int poolSize, FTPClientFactory factory) throws Exception { this.factory = factory; this.blockingQueue = new ArrayBlockingQueue(poolSize);
initPool(poolSize);
}
private void initPool(int maxPoolSize) throws Exception { int count = 0; while(count < maxPoolSize) { this.addObject();
count++;
}
}
@Override
public FTPClient borrowObject() throws Exception {
FTPClient client = blockingQueue.take(); if(client == null) {
client = factory.makeObject();
} else if(!factory.validateObject(client)) {
invalidateObject(client);
client = factory.makeObject();
} return client;
}
@Override
public void returnObject(FTPClient client) throws Exception { if ((client != null) && !blockingQueue.offer(client,2,TimeUnit.MINUTES)) { try {
factory.destroyObject(client);
} catch (Exception e) { throw e;
}
}
}
@Override
public void invalidateObject(FTPClient client) throws Exception {
blockingQueue.remove(client);
}
@Override
public void addObject() throws Exception {
blockingQueue.offer(factory.makeObject(), 2, TimeUnit.MINUTES);
}
public FTPClient reconnect() throws Exception { return factory.makeObject();
}
@Override
public int getNumIdle() { return blockingQueue.size();
}
@Override
public int getNumActive() { return DEFAULT_POOL_SIZE - getNumIdle();
} @Override
public void clear() throws Exception {
}
@Override
public void close() { try { while(blockingQueue.iterator().hasNext()) {
FTPClient client = blockingQueue.take();
factory.destroyObject(client);
}
} catch(Exception e) {
log.error("close ftp client pool failed...{}", e);
}
}
public void addObject(FTPClient ftpClient) throws Exception {
blockingQueue.put(ftpClient);
}
} 创建一个FTPClientFactory工厂类创建FTPClientFactory实现PoolableObjectFactory的接口,FTPClient工厂类,通过FTPClient工厂提供FTPClient实例的创建和销毁
@Slf4j@SuppressWarnings("all")public class FTPClientFactory implements PoolableObjectFactory { private FTPProperties ftpProperties; public FTPClientFactory(FTPProperties ftpProperties) { this.ftpProperties = ftpProperties;
} @Override
public FTPClient makeObject() throws Exception {
FTPClient ftpClient = new FTPClient();
ftpClient.setControlEncoding(ftpProperties.getEncoding());
ftpClient.setConnectTimeout(ftpProperties.getClientTimeout()); try {
ftpClient.connect(ftpProperties.getHost(), ftpProperties.getPort()); int reply = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) {
ftpClient.disconnect();
log.warn("FTPServer refused connection"); return null;
} boolean result = ftpClient.login(ftpProperties.getUsername(), ftpProperties.getPassword());
ftpClient.setFileType(ftpProperties.getTransferFileType()); if (!result) {
log.warn("ftpClient login failed... username is {}", ftpProperties.getUsername());
}
} catch (Exception e) {
log.error("create ftp connection failed...{}", e); throw e;
} return ftpClient;
} @Override
public void destroyObject(FTPClient ftpClient) throws Exception { try { if(ftpClient != null && ftpClient.isConnected()) {
ftpClient.logout();
}
} catch (Exception e) {
log.error("ftp client logout failed...{}", e); throw e;
} finally { if(ftpClient != null) {
ftpClient.disconnect();
}
}
} @Override
public boolean validateObject(FTPClient ftpClient) { try { return ftpClient.sendNoOp();
} catch (Exception e) {
log.error("Failed to validate client: {}");
} return false;
} @Override
public void activateObject(FTPClient obj) throws Exception { //Do nothing
} @Override
public void passivateObject(FTPClient obj) throws Exception { //Do nothing
}
} 创建FTPUtils.java的工具类FTPUtils.java中封装了上传、下载等方法,在项目启动的时候,在@PostConstruct注解的作用下通过执行init()的方法,创建FTPClientFactory工厂中,并初始化了FTPClientPool线程池,这样每次调用方法的时候,都直接从FTPClientPool中取出一个FTPClient对象
@Slf4j@Componentpublic class FTPUtils {
@Autowired
public static FTPClientPool ftpClientPool;
public static FTPClient ftpClient; private static FTPUtils ftpUtils; @Autowired
private FTPProperties ftpProperties;
@PostConstruct
public boolean init() {
FTPClientFactory factory = new FTPClientFactory(ftpProperties);
ftpUtils = this; try {
ftpClientPool = new FTPClientPool(factory);
} catch (Exception e) {
e.printStackTrace(); return false;
} return true;
}
public static FTPClient getFTPClient() throws Exception { //初始化的时候从队列中取出一个连接
if (ftpClient==null) { synchronized (ftpClientPool) {
ftpClient = ftpClientPool.borrowObject();
}
} return ftpClient;
}
public void complete() throws IOException {
ftpClient.completePendingCommand();
}
public void disconnect() throws Exception {
ftpClientPool.addObject(ftpClient);
}
public static boolean uploadFile(String remoteFile, InputStream input) { boolean result = false; try {
getFTPClient();
ftpClient.enterLocalPassiveMode();
result = ftpClient.storeFile(remoteFile, input);
input.close();
ftpClient.disconnect();
} catch (Exception e) {
e.printStackTrace();
} return result;
}
public static boolean uploadFile(String remoteFile, String localFile){
FileInputStream input = null; try {
input = new FileInputStream(new File(localFile));
} catch (FileNotFoundException e) {
e.printStackTrace();
} return uploadFile(remoteFile, input);
}
public boolean copyFile(String fromFile, String toFile) throws Exception {
InputStream in=getFileInputStream(fromFile);
getFTPClient(); boolean flag = ftpClient.storeFile(toFile, in);
in.close(); return flag;
}
public static InputStream getFileInputStream(String fileName) throws Exception {
ByteArrayOutputStream fos=new ByteArrayOutputStream();
getFTPClient();
ftpClient.retrieveFile(fileName, fos);
ByteArrayInputStream in=new ByteArrayInputStream(fos.toByteArray());
fos.close(); return in;
}
public static boolean downFile(String remoteFile, String localFile){ boolean result = false; try {
getFTPClient();
OutputStream os = new FileOutputStream(localFile);
ftpClient.retrieveFile(remoteFile, os);
ftpClient.logout();
ftpClient.disconnect();
result = true;
} catch (Exception e) {
e.printStackTrace();
} finally { try {
} catch (Exception e) {
e.printStackTrace();
}
} return result;
}
public static InputStream getInputStream(String filePath) throws Exception {
getFTPClient();
InputStream inputStream = ftpClient.retrieveFileStream(filePath); return inputStream;
}
public boolean rename(String fromFile,String toFile) throws Exception {
getFTPClient(); boolean result = ftpClient.rename(fromFile,toFile); return result;
}
public FTPFile[] getFiles(String dir) throws Exception {
getFTPClient();
FTPFile[] files = new FTPFile[0]; try {
files = ftpClient.listFiles(dir);
}catch (Throwable thr){
thr.printStackTrace();
} return files;
}
public FTPFile[] getFiles(String dir, FTPFileFilter filter) throws Exception {
getFTPClient();
FTPFile[] files = new FTPFile[0]; try {
files = ftpClient.listFiles(dir, filter);
}catch (Throwable thr){
thr.printStackTrace();
} return files;
}
public boolean makeDirectory(String remoteDir) throws Exception {
getFTPClient(); boolean result = false; try {
result = ftpClient.makeDirectory(remoteDir);
} catch (IOException e) {
e.printStackTrace();
} return result;
} public boolean mkdirs(String dir) throws Exception { boolean result = false; if (null == dir) { return result;
}
getFTPClient();
ftpClient.changeWorkingDirectory("/");
StringTokenizer dirs = new StringTokenizer(dir, "/");
String temp = null; while (dirs.hasMoreElements()) {
temp = dirs.nextElement().toString(); //创建目录
ftpClient.makeDirectory(temp); //进入目录
ftpClient.changeWorkingDirectory(temp);
result = true;
}
ftpClient.changeWorkingDirectory("/"); return result;
}
}创建FtpClientTest.java测试类上传一张图片到FTP服务器,并将文件重新命名为hrabbit.jpg,代码如下:
@RunWith(SpringRunner.class)@SpringBootTestpublic class FtpClientTest {
@Test
public void uploadFile(){ boolean flag = FTPUtils.uploadFile("hrabbit.jpg", "/Users/mrotaku/Downloads/klklklkl_4x.jpg");
Assert.assertEquals(true, flag);
}
}程序完美运行,这时候我们查看我们的FTP服务器,http://localhost:8866/hrabbit.jpg
image.png
作者:hrabbits
链接:https://www.jianshu.com/p/6270b2308c4e



