巧用GenericObjectPool创建自定义对象池
作者:京东物流 高圆庆
1 前言
通常一个对象创建、销毁非常耗时的时候,我们不会频繁的创建和销毁它,而是考虑复用。复用对象的一种做法就是对象池,将创建好的对象放入池中维护起来,下次再用的时候直接拿池中已经创建好的对象继续用,这就是池化的思想。在java中,有很多池管理的概念,典型的如线程池,数据库连接池,socket连接池。本文章讲介绍apache提供的通用对象池框架GenericObjectPool,以及基于GenericObjectPool实现的sftp连接池在国际物流调度履约系统中的应用。
2 GenericObjectPool剖析
Apache Commons Pool是一个对象池的框架,他提供了一整套用于实现对象池化的API。它提供了三种对象池:GenericKeyedObjectPool,SoftReferenceObjectPool和GenericObjectPool,其中GenericObjectPool是我们最常用的对象池,内部实现也最复杂。GenericObjectPool的UML图如下所示:

2.1 核心接口ObjectPool
从图中可以看出,GenericObjectPool实现了ObjectPool接口,而ObjectPool就是对象池的核心接口,它定义了一个对象池应该实现的行为。
- addObject方法:往池中添加一个对象
- borrowObject方法:从池中借走到一个对象
- returnObject方法:把对象归还给对象池
- invalidateObject:验证对象的有效性
- getNumIdle:返回对象池中有多少对象是空闲的,也就是能够被借走的对象的数量。
- getNumActive:返回对象池中有对象对象是活跃的,也就是已经被借走的,在使用中的对象的数量。
- clear:清理对象池。注意是清理不是清空,该方法要求的是,清理所有空闲对象,释放相关资源。
- close:关闭对象池。这个方法可以达到清空的效果,清理所有对象以及相关资源。
2.2 对象工厂BasePooledObjectFactory
对象的创建需要通过对象工厂来创建,对象工厂需要实现BasePooledObjectFactory接口。ObjectPool接口中往池中添加一个对象,就需要使用对象工厂来创建一个对象。该接口说明如下:
public interface PooledObjectFactory<T> {/*** 创建一个可由池提供服务的实例,并将其封装在由池管理的PooledObject中。*/PooledObject<T> makeObject() throws Exception;/*** 销毁池不再需要的实例*/void destroyObject(PooledObject<T> p) throws Exception;/*** 确保实例可以安全地由池返回*/boolean validateObject(PooledObject<T> p);/*** 重新初始化池返回的实例*/void activateObject(PooledObject<T> p) throws Exception;/*** 取消初始化要返回到空闲对象池的实例*/void passivateObject(PooledObject<T> p) throws Exception;}
2.3 配置类GenericObjectPoolConfig
GenericObjectPoolConfig是封装GenericObject池配置的简单“结构”,此类不是线程安全的;它仅用于提供创建池时使用的属性。大多数情况,可以使用GenericObjectPoolConfig提供的默认参数就可以满足日常的需求,GenericObjectPoolConfig是一个抽象类,实际应用中需要新建配置类,然后继承它。
2.4 工作原理流程
- 构造方法
当我们执行构造方法时,主要工作就是创建了一个存储对象的LinkedList类型容器,也就是概念意义上的“池” - 从对象池中获取对象
获取池中的对象是通过borrowObject()命令,源码比较复杂,简单而言就是去LinkedList中获取一个对象,如果不存在的话,要调用构造方法中第一个参数Factory工厂类的makeObject()方法去创建一个对象再获取,获取到对象后要调用validateObject方法判断该对象是否是可用的,如果是可用的才拿去使用。LinkedList容器减一 - 归还对象到线程池
简单而言就是先调用validateObject方法判断该对象是否是可用的,如果可用则归还到池中,LinkedList容器加一,如果是不可以的则则调用destroyObject方法进行销毁
上面三步就是最简单的流程,由于取和还的流程步骤都在borrowObject和returnObject方法中固定的,所以我们只要重写Factory工厂类的makeObject()和validateObject以及destroyObject方法即可实现最简单的池的管理控制,通过构造方法传入该Factory工厂类对象则可以创建最简单的对象池管理类。这算是比较好的解耦设计模式,借和还的流程如下图所示:

3 开源框架如何使用GenericObjectPool
redis的java客户端jedis就是基于Apache Commons Pool对象池的框架来实现的。
3.1 对象工厂类JedisFactory
对象工厂类只需实现activateObject、destroyObject、makeObject、validateObject方法即可,源码如下:
class JedisFactory implements PooledObjectFactory<Jedis> {private final String host;private final int port;private final int timeout;private final int newTimeout;private final String password;private final int database;private final String clientName;public JedisFactory(String host, int port, int timeout, String password, int database) {this(host, port, timeout, password, database, (String)null);}public JedisFactory(String host, int port, int timeout, String password, int database, String clientName) {this(host, port, timeout, timeout, password, database, clientName);}public JedisFactory(String host, int port, int timeout, int newTimeout, String password, int database, String clientName) {this.host = host;this.port = port;this.timeout = timeout;this.newTimeout = newTimeout;this.password = password;this.database = database;this.clientName = clientName;}public void activateObject(PooledObject<Jedis> pooledJedis) throws Exception {BinaryJedis jedis = (BinaryJedis)pooledJedis.getObject();if (jedis.getDB() != (long)this.database) {jedis.select(this.database);}}public void destroyObject(PooledObject<Jedis> pooledJedis) throws Exception {BinaryJedis jedis = (BinaryJedis)pooledJedis.getObject();if (jedis.isConnected()) {try {try {jedis.quit();} catch (Exception var4) {}jedis.disconnect();} catch (Exception var5) {}}}public PooledObject<Jedis> makeObject() throws Exception {Jedis jedis = new Jedis(this.host, this.port, this.timeout, this.newTimeout);jedis.connect();if (null != this.password) {jedis.auth(this.password);}if (this.database != 0) {jedis.select(this.database);}if (this.clientName != null) {jedis.clientSetname(this.clientName);}return new DefaultPooledObject(jedis);}public void passivateObject(PooledObject<Jedis> pooledJedis) throws Exception {}public boolean validateObject(PooledObject<Jedis> pooledJedis) {BinaryJedis jedis = (BinaryJedis)pooledJedis.getObject();try {return jedis.isConnected() && jedis.ping().equals("PONG");} catch (Exception var4) {return false;}}}
3.2 配置类JedisPoolConfig
public class JedisPoolConfig extends GenericObjectPoolConfig {public JedisPoolConfig() {this.setTestWhileIdle(true);this.setMinEvictableIdleTimeMillis(60000L);this.setTimeBetweenEvictionRunsMillis(30000L);this.setNumTestsPerEvictionRun(-1);}}
4 国际物流履约系统中的应用
在国际物流履约系统中,我们和客户交互文件经常使用sftp服务器,因为创建sftp服务器的连接比较耗时,所以基于Apache Commons Pool对象池的框架来实现的我们自己的sftp链接池。
4.1 sftp对象池
SftpPool比较简单,直接继承GenericObjectPool。
public class SftpPool extends GenericObjectPool<Sftp> {public SftpPool(SftpFactory factory, SftpPoolConfig config, SftpAbandonedConfig abandonedConfig) {super(factory, config, abandonedConfig);}}
4.2 对象工厂SftpFactory
这是基于Apache Commons Pool框架实现自定义对象池的核心类,代码如下:
public class SftpFactory extends BasePooledObjectFactory<Sftp> {private static final String CHANNEL_TYPE = "sftp";private static Properties sshConfig = new Properties();private String host;private int port;private String username;private String password;static {sshConfig.put("StrictHostKeyChecking", "no");}@Overridepublic Sftp create() {try {JSch jsch = new JSch();Session sshSession = jsch.getSession(username, host, port);sshSession.setPassword(password);sshSession.setConfig(sshConfig);sshSession.connect();ChannelSftp channel = (ChannelSftp) sshSession.openChannel(CHANNEL_TYPE);channel.connect();log.info("sftpFactory创建sftp");return new Sftp(channel);} catch (JSchException e) {log.error("连接sftp失败:", e);throw new BizException(ResultCodeEnum.SFTP_EXCEPTION);}}/*** @param sftp 被包装的对象* @return 对象包装器*/@Overridepublic PooledObject<Sftp> wrap(Sftp sftp) {return new DefaultPooledObject<>(sftp);}/*** 销毁对象* @param p 对象包装器*/@Overridepublic void destroyObject(PooledObject<Sftp> p) {log.info("开始销毁channelSftp");if (p!=null) {Sftp sftp = p.getObject();if (sftp!=null) {ChannelSftp channelSftp = sftp.getChannelSftp();if (channelSftp!=null) {channelSftp.disconnect();log.info("销毁channelSftp成功");}}}}/*** 检查连接是否可用** @param p 对象包装器* @return {@code true} 可用,{@code false} 不可用*/@Overridepublic boolean validateObject(PooledObject<Sftp> p) {if (p!=null) {Sftp sftp = p.getObject();if (sftp!=null) {try {sftp.getChannelSftp().cd("./");log.info("验证连接是否可用,结果为true");return true;} catch (SftpException e) {log.info("验证连接是否可用,结果为false",e);return false;}}}log.info("验证连接是否可用,结果为false");return false;}public static class Builder {private String host;private int port;private String username;private String password;public SftpFactory build() {return new SftpFactory(host, port, username, password);}public Builder host(String host) {this.host = host;return this;}public Builder port(int port) {this.port = port;return this;}public Builder username(String username) {this.username = username;return this;}public Builder password(String password) {this.password = password;return this;}}}
4.3 配置类SftpPoolConfig
配置类继承了GenericObjectPoolConfig,可继承该类的默认属性,也可自定义配置参数。
public class SftpPoolConfig extends GenericObjectPoolConfig<Sftp> {public static class Builder {private int maxTotal;private int maxIdle;private int minIdle;private boolean lifo;private boolean fairness;private long maxWaitMillis;private long minEvictableIdleTimeMillis;private long evictorShutdownTimeoutMillis;private long softMinEvictableIdleTimeMillis;private int numTestsPerEvictionRun;private EvictionPolicy<Sftp> evictionPolicy; // 仅2.6.0版本commons-pool2需要设置private String evictionPolicyClassName;private boolean testOnCreate;private boolean testOnBorrow;private boolean testOnReturn;private boolean testWhileIdle;private long timeBetweenEvictionRunsMillis;private boolean blockWhenExhausted;private boolean jmxEnabled;private String jmxNamePrefix;private String jmxNameBase;public SftpPoolConfig build() {SftpPoolConfig config = new SftpPoolConfig();config.setMaxTotal(maxTotal);config.setMaxIdle(maxIdle);config.setMinIdle(minIdle);config.setLifo(lifo);config.setFairness(fairness);config.setMaxWaitMillis(maxWaitMillis);config.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);config.setEvictorShutdownTimeoutMillis(evictorShutdownTimeoutMillis);config.setSoftMinEvictableIdleTimeMillis(softMinEvictableIdleTimeMillis);config.setNumTestsPerEvictionRun(numTestsPerEvictionRun);config.setEvictionPolicy(evictionPolicy);config.setEvictionPolicyClassName(evictionPolicyClassName);config.setTestOnCreate(testOnCreate);config.setTestOnBorrow(testOnBorrow);config.setTestOnReturn(testOnReturn);config.setTestWhileIdle(testWhileIdle);config.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);config.setBlockWhenExhausted(blockWhenExhausted);config.setJmxEnabled(jmxEnabled);config.setJmxNamePrefix(jmxNamePrefix);config.setJmxNameBase(jmxNameBase);return config;}}
4.4 SftpClient配置类
读取配置文件,创建SftpFactory、SftpPoolConfig、SftpPool,代码如下:
@Configuration@ConditionalOnClass(SftpPool.class)@EnableConfigurationProperties(SftpClientProperties.class)public class SftpClientAutoConfiguration {@Bean@ConditionalOnMissingBeanpublic ISftpClient sftpClient(SftpClientProperties sftpClientProperties) {if (sftpClientProperties.isMultiple()) {MultipleSftpClient multipleSftpClient = new MultipleSftpClient();sftpClientProperties.getClients().forEach((name, properties) -> {SftpFactory sftpFactory = createSftpFactory(properties);SftpPoolConfig sftpPoolConfig = createSftpPoolConfig(properties);SftpAbandonedConfig sftpAbandonedConfig = createSftpAbandonedConfig(properties);SftpPool sftpPool = new SftpPool(sftpFactory, sftpPoolConfig, sftpAbandonedConfig);ISftpClient sftpClient = new SftpClient(sftpPool);multipleSftpClient.put(name, sftpClient);});return multipleSftpClient;}SftpFactory sftpFactory = createSftpFactory(sftpClientProperties);SftpPoolConfig sftpPoolConfig = createSftpPoolConfig(sftpClientProperties);SftpAbandonedConfig sftpAbandonedConfig = createSftpAbandonedConfig(sftpClientProperties);SftpPool sftpPool = new SftpPool(sftpFactory, sftpPoolConfig, sftpAbandonedConfig);return new SftpClient(sftpPool);}public SftpFactory createSftpFactory(SftpClientProperties properties) {return new SftpFactory.Builder().host(properties.getHost()).port(properties.getPort()).username(properties.getUsername()).password(properties.getPassword()).build();}public SftpPoolConfig createSftpPoolConfig(SftpClientProperties properties) {SftpClientProperties.Pool pool = properties.getPool();return new SftpPoolConfig.Builder().maxTotal(pool.getMaxTotal()).maxIdle(pool.getMaxIdle()).minIdle(pool.getMinIdle()).lifo(pool.isLifo()).fairness(pool.isFairness()).maxWaitMillis(pool.getMaxWaitMillis()).minEvictableIdleTimeMillis(pool.getMinEvictableIdleTimeMillis()).evictorShutdownTimeoutMillis(pool.getEvictorShutdownTimeoutMillis()).softMinEvictableIdleTimeMillis(pool.getSoftMinEvictableIdleTimeMillis()).numTestsPerEvictionRun(pool.getNumTestsPerEvictionRun()).evictionPolicy(null).evictionPolicyClassName(DefaultEvictionPolicy.class.getName()).testOnCreate(pool.isTestOnCreate()).testOnBorrow(pool.isTestOnBorrow()).testOnReturn(pool.isTestOnReturn()).testWhileIdle(pool.isTestWhileIdle()).timeBetweenEvictionRunsMillis(pool.getTimeBetweenEvictionRunsMillis()).blockWhenExhausted(pool.isBlockWhenExhausted()).jmxEnabled(pool.isJmxEnabled()).jmxNamePrefix(pool.getJmxNamePrefix()).jmxNameBase(pool.getJmxNameBase()).build();}public SftpAbandonedConfig createSftpAbandonedConfig(SftpClientProperties properties) {SftpClientProperties.Abandoned abandoned = properties.getAbandoned();return new SftpAbandonedConfig.Builder().removeAbandonedOnBorrow(abandoned.isRemoveAbandonedOnBorrow()).removeAbandonedOnMaintenance(abandoned.isRemoveAbandonedOnMaintenance()).removeAbandonedTimeout(abandoned.getRemoveAbandonedTimeout()).logAbandoned(abandoned.isLogAbandoned()).requireFullStackTrace(abandoned.isRequireFullStackTrace()).logWriter(new PrintWriter(System.out)).useUsageTracking(abandoned.isUseUsageTracking()).build();}}
4.5 对象SftpClient
SftpClient是实际工作的类,从SftpClient 中可获取到一个sftp链接,使用完成后,归还给sftpPool。SftpClient代码如下:
public class SftpClient implements ISftpClient {private SftpPool sftpPool;/*** 从sftp连接池获取连接并执行操作** @param handler sftp操作*/@Overridepublic void open(ISftpClient.Handler handler) {Sftp sftp = null;try {sftp = sftpPool.borrowObject();ISftpClient.Handler policyHandler = new DelegateHandler(handler);policyHandler.doHandle(sftp);} catch (Exception e) {log.error("sftp异常:", e);throw new BizException(ResultCodeEnum.SFTP_EXCEPTION);} finally {if (sftp != null) {sftpPool.returnObject(sftp);}}}@AllArgsConstructorstatic class DelegateHandler implements ISftpClient.Handler {private ISftpClient.Handler target;@Overridepublic void doHandle(Sftp sftp) {try {target.doHandle(sftp);} catch (Exception e) {log.error("sftp异常:", e);throw new BizException(ResultCodeEnum.SFTP_EXCEPTION);}}}}
4.6 实战代码示例
通过sftp上传文件到XX服务器
//通过SFTP上传到XX((MultipleSftpClient) sftpClient).choose("XX");sftpClient.open(sftp -> {boolean exist = sftp.isExist(inventoryPath);if(!exist){sftp.mkdirs(inventoryPath);}// 执行sftp操作InputStream is = new FileInputStream(oneColumnCSVFile);sftp.upload(inventoryPath, titleName, is);log.info("inventory upload over");});
5 总结
通过本文的介绍可以知道,Apache Commons Pool定义了一个对象池的行为,提供了可扩展的配置类和对象工厂,封装了对象创建、从池中获取对象、归还对象的核心流程。还介绍了开源框架Jedis是如何基于GenericObjectPool来实现的连接池。最后介绍了国际物流履约系统中是如何基于GenericObjectPool来管理Sftp连接的。
掌握了GenericObjectPool的核心原理,我们就可以通过实现几个关键的接口,创建一个对象池管理工具,在项目中避免了对象的频繁创建和销毁,从而显著提升程序的性能。
巧用GenericObjectPool创建自定义对象池的更多相关文章
- 利用commons-pool2自定义对象池
一.为什么使用对象池 恰当地使用对象池化技术,可以有效地减少对象生成和初始化时的消耗,提高系统的运行效率.commons-pool2是Apache下一个开源的公共资源池.我们可以根据它来快速的建立 ...
- javascript创建自定义对象和prototype
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 用js创建XMLHttpRequest对象池[转]
//使用literal语法定义一个对象:XMLHttp var XMLHttp = { //定义第一个属性,该属性用于缓存XMLHttpRequest对象的数组 XMLHttpRequestPool: ...
- Javascript 中创建自定义对象的方法(设计模式)
Javascript 中创建对象,可以有很多种方法. Object构造函数/对象字面量: 抛开设计模式不谈,使用最基本的方法,就是先调用Object构造函数创建一个对象,然后给对象添加属性. var ...
- JS 创建自定义对象的方式方法
一.概述 还记得刚开始做项目的时候,看到别人封装的js工具类百思不得其解,看来看去看不懂,深挖一下,其实就是自己没有耐下心去看,但是遇到问题不解决,总会遇到的,今天还是遇到了,就去找了找帖子,重新思考 ...
- JavaScript中创建自定义对象的方法
本文内容参考JavaScript高级程序设计(第3版)第6章:面向对象的程序设计 ECMA-262中把对象定义为:“无序属性的集合,其属性可以包含基本值.对象或者函数.”我所理解的就是对象就是一个结构 ...
- JS创建自定义对象
普通对象的创建: 创建对象: 1.people = new Object(); people.name = "lin"; people.age = "26“; 2.创建字 ...
- JS中创建自定义对象的方法
1.直接给对象扩充属性和方法: 2.对象字面量: 3.工厂方式: 4.构造函数方式: 5.原型方式: 6.混合方式. <script> // 1.直接给对象扩充属性和方法; var cat ...
- JavaScript 创建和浅析自定义对象
在Js中,除了Array.Date.Number等内置对象外,开发者可以通过Js代码创建自己的对象. 目录 1. 对象特性:描述对象的特性 2. 创建对象方式:对象直接量.new 构造函数.Objec ...
- common-pool2对象池(连接池)的介绍及使用
我们在服务器开发的过程中,往往会有一些对象,它的创建和初始化需要的时间比较长,比如数据库连接,网络IO,大数据对象等.在大量使用这些对象时,如果不采用一些技术优化,就会造成一些不可忽略的性能影响.一种 ...
随机推荐
- 2022 IDC中国未来企业大奖优秀奖颁布,华为云数据库助力德邦快递获奖
摘要:华为云数据库助力德邦快递打造的"基于数智融合的一站式物流供应链平台"项目从500多个项目中脱颖而出,荣获2022 IDC中国未来企业大奖优秀奖"未来智能领军者&qu ...
- 认识一下MRS里的“中间人”Alluxio
摘要:Alluxio在mrs的数据处理生态中处于计算和存储之间,为上层spark.presto.mapredue.hive计算框架提供了数据抽象层,计算框架可以通过统一的客户端api和全局命名空间访问 ...
- 如何给MindSpore添加一个新的硬件后端?快速构建测试环境!
摘要:介绍如何给MindSpore添加一个新的硬件后端. 本文分享自华为云社区<如何给MindSpore添加一个新的硬件后端?快速构建测试环境!>,原文作者:HWCloudAI. Mind ...
- 一文带你 GNN 从入门到起飞,做一个饭盆最稳 GNN 饭人!
摘要:本文介绍了图神经网络在学界和业界的发展情况,并给出了图神经网络的基本概念与表示形式,总结了图神经网络的变体,最后介绍了华为云图神经网络框架. 本文分享自华为云社区<干饭人,干饭魂,搞懂图神 ...
- SimpleDateFormat线程不安全了?这里有5种解决方案
摘要:我们知道SimpleDateFormat是线程不安全,本文会介绍多种解决方案来保证线程安全. 本文分享自华为云社区<java的SimpleDateFormat线程不安全出问题了,虚竹教你多 ...
- 结MySQL 的一些知识点:MySQL 安装
MySQL 安装 所有平台的 MySQL 下载地址为: MySQL 下载 . 挑选你需要的 MySQL Community Server 版本及对应的平台. **注意:**安装过程我们需要通过开启管理 ...
- C++11实用特性2
1 可调用对象包装器.绑定器 1可调用对象 C++中的可调用对象分为四类: 函数指针: 任何一个函数都可以抽象成一个函数指针 int print(int a, double b) { cout < ...
- 揭秘2022冬奥黑科技,阿里云视频云「Cloud ME」如何实现全息会面?
2022北京冬奥会本是一届非凡的存在,这是有史以来第一次将奥运会所需的全部核心系统全面上云,以数字化技术创造奥运的新纪元. 但绿色奥运不止如此,在面临 Covid-19 限制和物理隔阂之下,千里之外, ...
- 【每日一题】23.Removal (计数DP)
补题链接:Here 计数DP讲解:Here 这是一个计数类的dp dp[i][j]表示前i个数字中,删除j个元素的方案数 很容易得到转移方程:\(f[i][j] = f[i - 1][j - 1] + ...
- L1-048 矩阵A乘以B (15分)
给定两个矩阵A和B,要求你计算它们的乘积矩阵 \(AB\).需要注意的是,只有规模匹配的矩阵才可以相乘.即若A有 \(R_a\) 行.\(C_a\) 列,B有 \(R_b\) 行.\(C_b\) 列, ...