回顾
上一章讲到,通过FTPClient进行上长传和下载操作,但是频繁new FTPClient进行连接操作,是否会导致资源浪费呢。
流程排查
FTPClient ftpClient = new FTPClient();
try {
ftpClient.connect(props.getHost(), props.getPort());
ftpClient.login(props.getUsername(), props.getPassword());
我们知道每次使用前,需要连接,连接需要new一个对象,并且connect和login。我们来看下,这几个方法核心实现:
FTPClient
public FTPClient()
{
__initDefaults();
__dataTimeout = -1;
__remoteVerificationEnabled = true;
__parserFactory = new DefaultFTPFileEntryParserFactory();
__configuration = null;
__listHiddenFiles = false;
__useEPSVwithIPv4 = false;
__random = new Random();
__passiveLocalHost = null;
}
private void __initDefaults()
{
__dataConnectionMode = ACTIVE_LOCAL_DATA_CONNECTION_MODE;
__passiveHost = null;
__passivePort = -1;
__activeExternalHost = null;
__reportActiveExternalHost = null;
__activeMinPort = 0;
__activeMaxPort = 0;
__fileType = FTP.ASCII_FILE_TYPE;
__fileStructure = FTP.FILE_STRUCTURE;
__fileFormat = FTP.NON_PRINT_TEXT_FORMAT;
__fileTransferMode = FTP.STREAM_TRANSFER_MODE;
__restartOffset = 0;
__systemName = null;
__entryParser = null;
__entryParserKey = "";
__featuresMap = null;
}
简单的初始化数据操作。这个构造函数没有什么复杂操作,也没任何读取缓存操作,就是new一个对象。
connect
// helper method to allow code to be shared with connect(String,...) methods
private void _connect(InetAddress host, int port, InetAddress localAddr, int localPort)
throws SocketException, IOException
{
_socket_ = _socketFactory_.createSocket();
...隐藏判定、赋值...
_socket_.connect(new InetSocketAddress(host, port), connectTimeout);
_connectAction_();
}
看起来有三个功能
1、代码复用,shared code
2、创建一个socket。socketFactory.createSocket();
3、连接socket。 socket.connect(new InetSocketAddress(host, port), connectTimeout);
我们可以进一步看一下connect方法。发现是常规的socket认证连接,故可以判定,整个机制中无ftp连接池复用概念。每次使用FTPClient都需要创建对象、连接、并释放
public void connect(SocketAddress endpoint, int timeout) throws IOException {
...隐藏判定、赋值...
InetSocketAddress epoint = (InetSocketAddress) endpoint;
InetAddress addr = epoint.getAddress ();
int port = epoint.getPort();
checkAddress(addr, "connect");
SecurityManager security = System.getSecurityManager();
if (security != null) {
if (epoint.isUnresolved())
security.checkConnect(epoint.getHostName(), port);
else
security.checkConnect(addr.getHostAddress(), port);
}
if (!created)
createImpl(true);
if (!oldImpl)
impl.connect(epoint, timeout);
else if (timeout == 0) {
if (epoint.isUnresolved())
impl.connect(addr.getHostName(), port);
else
impl.connect(addr, port);
} else
throw new UnsupportedOperationException("SocketImpl.connect(addr, timeout)");
connected = true;
/*
* If the socket was not bound before the connect, it is now because
* the kernel will have picked an ephemeral port & a local address
*/
bound = true;
}
引入连接池机制
依赖
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.4.2</version>
</dependency>
如何使用
我们考虑下,一个连接池核心操作应该具备哪些
1、初始化池资源
2、生产、获取、销毁对象
3、监控忙碌和空闲资源,可进行动态分配
4、监控资源有效性
建立配置信息
从yml配置文件中读取用户名、密码、地址、端口信息等。
@Data
@ConfigurationProperties(prefix = "ftp")
static class FtpConfigProperties {
private String host = "localhost";
private int port = FTPClient.DEFAULT_PORT;
private String username;
private String password;
private int bufferSize = 8096;
/**
* 初始化连接数
*/
private Integer initialSize = 1;
}
初始化池资源
通过FtpClientPooledObjectFactory这个类,初始化数据池容器。核心方法为
new GenericObjectPool<>();
@Slf4j
@Configuration
@ConditionalOnClass({GenericObjectPool.class, FTPClient.class})
@ConditionalOnProperty(value = "ftp.enabled", havingValue = "true")
@EnableConfigurationProperties(FTPConfiguration.FtpConfigProperties.class)
public class FTPConfiguration {
//资源池容器,为接口泛型
private ObjectPool<FTPClient> pool;
//构造是传入,通过ftp选择是否加载此bean
public FTPConfiguration(FtpConfigProperties props) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
//设置最小线程数量
poolConfig.setMinIdle(1);
//设置最大空闲时间,当大于50s时,移除连接对象,直至只剩最后一个
poolConfig.setSoftMinEvictableIdleTimeMillis(50000);
//巡检时间,每30秒巡检一次
poolConfig.setTimeBetweenEvictionRunsMillis(30000);
//生产数据池
pool = new GenericObjectPool<>(new FtpClientPooledObjectFactory(props), poolConfig);
//设置初始值和最大值
preLoadingFtpClient(props.getInitialSize(), poolConfig.getMaxIdle());
}
生产、获取、销毁对象
对象的生产、获取、销毁通过接口的方式实现,即上面interface ObjectPool<T>
我们先看下它的方法
public interface ObjectPool<T> {
//获取
T borrowObject() throws Exception, NoSuchElementException,
IllegalStateException;
//归还
void returnObject(T obj) throws Exception;
//验证对象是否有效
void invalidateObject(T obj) throws Exception;
//添加对象
void addObject() throws Exception, IllegalStateException,
UnsupportedOperationException;
/***获取实例和活动实例,清理资源和关闭数据池***/
int getNumIdle();
int getNumActive();
void clear() throws Exception, UnsupportedOperationException;
void close();
}