下面看看如何使用Commons Pool如何實現自己的對象池。創建自己的對象池大致需要以下工作,
1. 首先你已經編寫好了你的資源對象(這部分不屬於池化內容),之後編寫實現apache的PooledObjectFactory<T>接口的Factory類,這是編寫自己對象池最主要的工作。你的Factory可能需要添加一些用於池化對象的初始化 ,池化對象的驗證等參數作為成員變量。
2. 編寫自己的Pool類,讓其繼承或者內部引用apache的GenericObjectPool<T>,GenericObjectPool實現了ObjectPool接口,已經封裝了對池化對象的生命周期管理邏輯
3. 可選部分,繼承apache的GenericObjectPoolConfig,重寫構造器,添加一些適合自己業務場景的初始化參數。
我們以Jedis的源碼為例,學習它的實現。我們先看下使用JedisPool操作Redis的簡單例子
package com.eg.test.redis;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
public class TestPool {
public static void main(String[] args) {
//JedisPoolConfig繼承apache的GenericObjectPoolConfig,配置Pool的相關參數如下:
JedisPoolConfig config = new JedisPoolConfig();
//如果賦值為-1,則表示不限制;如果pool已經分配了maxActive個jedis實例,則此時pool的狀態為exhausted(耗盡)。
config.setMaxTotal(500);
//控制一個pool最多有多少個狀態為idle(空閒的)的jedis實例。
config.setMaxIdle(5);
//表示當borrow(引入)一個jedis實例時,最大的等待時間,如果超過等待時間,則直接拋出JedisConnectionException;
config.setMaxWaitMillis(30000);;
//在borrow一個jedis實例時,是否提前進行validate操作;如果為true,則得到的jedis實例均是可用的;
config.setTestOnBorrow(true);
JedisPool pool = new JedisPool(config, "192.168.2.191", 8888);
//從pool中獲取對象
Jedis jedis = pool.getResource();
String value = jedis.get("someKey");
}
}
首先看JedisFactory的實現:
class JedisFactory implements PooledObjectFactory<Jedis> {
private final AtomicReference<HostAndPort> hostAndPort = new AtomicReference<HostAndPort>();
private final int connectionTimeout;
private final int soTimeout;
//省略構造函數,都是一些初始化成員變量的操作
@Override
public void activateObject(PooledObject<Jedis> pooledJedis) throws Exception {
final BinaryJedis jedis = pooledJedis.getObject();
if (jedis.getDB() != database) {
jedis.select(database);
}
}
@Override
public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {
final BinaryJedis jedis = pooledJedis.getObject();
if (jedis.isConnected()) {
try {
try {
jedis.quit();
} catch (Exception e) {
}
jedis.disconnect();
} catch (Exception e) {
}
}
}
@Override
public PooledObject<Jedis> makeObject() throws Exception {
final HostAndPort hostAndPort = this.hostAndPort.get();
final Jedis jedis = new Jedis(hostAndPort.getHost(), hostAndPort.getPort(), connectionTimeout, soTimeout, ssl,
sslSocketFactory, sslParameters, hostnameVerifier);
try {
jedis.connect();
if (null != this.password) {
jedis.auth(this.password);
}
if (database != 0) {
jedis.select(database);
}
if (clientName != null) {
jedis.clientSetname(clientName);
}
} catch (JedisException je) {
jedis.close();
throw je;
}
return new DefaultPooledObject<Jedis>(jedis);
}
@Override
public void passivateObject(PooledObject<Jedis> pooledJedis) throws Exception {
// TODO maybe should select db 0? Not sure right now.
}
@Override
public boolean validateObject(PooledObject<Jedis> pooledJedis) {
final BinaryJedis jedis = pooledJedis.getObject();
try {
HostAndPort hostAndPort = this.hostAndPort.get();
String connectionHost = jedis.getClient().getHost();
int connectionPort = jedis.getClient().getPort();
return hostAndPort.getHost().equals(connectionHost) && hostAndPort.getPort() == connectionPort
&& jedis.isConnected() && jedis.ping().equals("PONG");
} catch (final Exception e) {
return false;
}
}
}
我們看到JedisFactory代碼較少,但是邏輯很清晰。該Factory將作為ObjectPool的成員變量,其中四個重寫的方法被ObjectPool管理對象生命周期的時候調用。makeobject()方法負責創建Jedis實例,成功調用connect()方法建立有狀態的socket連接之後,返回一個包裝了jedis的DefaultPooledObject對象,DefaultPooledObject實現了關於統計池化對象狀態信息的PooledObject接口。validateObject()方法用於對對象狀態的檢驗,Jedis對象的狀態通過socket的ping-pong來驗證連接是否正常。destroyObject()方法用來銷毀對象,Jedis對象將會斷開連接,回收資源。
再看JedisPool的實現,由於JedisPool繼承Pool<T>,所以我們主要看Pool<T>的部分代碼:
public abstract class Pool<T> implements Closeable {
protected GenericObjectPool<T> internalPool;
public Pool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
initPool(poolConfig, factory);
}
public boolean isClosed() {
return this.internalPool.isClosed();
}
public void initPool(final GenericObjectPoolConfig poolConfig, PooledObjectFactory<T> factory) {
if (this.internalPool != null) {
try {
closeInternalPool();
} catch (Exception e) {
}
}
this.internalPool = new GenericObjectPool<T>(factory, poolConfig);
}
public T getResource() {
try {
return internalPool.borrowObject();
} catch (NoSuchElementException nse) {
throw new JedisException("Could not get a resource from the pool", nse);
} catch (Exception e) {
throw new JedisConnectionException("Could not get a resource from the pool", e);
}
}
protected void returnResourceObject(final T resource) {
if (resource == null) {
return;
}
try {
internalPool.returnObject(resource);
} catch (Exception e) {
throw new JedisException("Could not return the resource to the pool", e);
}
}
public void addObjects(int count) {
try {
for (int i = 0; i < count; i++) {
this.internalPool.addObject();
}
} catch (Exception e) {
throw new JedisException("Error trying to add idle objects", e);
}
}
}
JedisPool通過內部引用GenericObjectPool,包裝其接口的裝飾者模式,相比繼承來說這種模式更加靈活。JedisPool的構造方法需要將JedisFactory以及JedisPoolConfig創建標准的ObjectPool作為自己的成員變量。所以pool.getResource()方法的背後還是調用PoolObject.borrowObject()。
最後我們稍微看下JedisPoolConfig,只是做了一些預初始化參數的工作。
public class JedisPoolConfig extends GenericObjectPoolConfig {
public JedisPoolConfig() {
// defaults to make your life with connection pool easier :)
setTestWhileIdle(true);
setMinEvictableIdleTimeMillis(60000);
setTimeBetweenEvictionRunsMillis(30000);
setNumTestsPerEvictionRun(-1);
}
}