generic(巧用GenericObjectPool创建自定义对象池)
generic(巧用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接口中往池中添加一个对象,就需要使用对象工厂来创建一个对象。该接口说明如下:
publicinterfacePooledObjectFactory{/** *创建一个可由池提供服务的实例,并将其封装在由池管理的PooledObject中。 */ PooledObjectmakeObject()throwsException;/** *销毁池不再需要的实例 */ voiddestroyObject(PooledObjectp)throwsException;/** *确保实例可以安全地由池返回 */ booleanvalidateObject(PooledObjectp);/** *重新初始化池返回的实例 */ voidactivateObject(PooledObjectp)throwsException;/** *取消初始化要返回到空闲对象池的实例 */ voidpassivateObject(PooledObjectp)throwsException; }
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方法即可,源码如下:
classJedisFactoryimplementsPooledObjectFactory{privatefinalStringhost;privatefinalintport;privatefinalinttimeout;privatefinalintnewTimeout;privatefinalStringpassword;privatefinalintdatabase;privatefinalStringclientName;publicJedisFactory(Stringhost,intport,inttimeout,Stringpassword,intdatabase){this(host,port,timeout,password,database,(String)null); }publicJedisFactory(Stringhost,intport,inttimeout,Stringpassword,intdatabase,StringclientName){this(host,port,timeout,timeout,password,database,clientName); }publicJedisFactory(Stringhost,intport,inttimeout,intnewTimeout,Stringpassword,intdatabase,StringclientName){this.host=host;this.port=port;this.timeout=timeout;this.newTimeout=newTimeout;this.password=password;this.database=database;this.clientName=clientName; }publicvoidactivateObject(PooledObjectpooledJedis)throwsException{ BinaryJedisjedis=(BinaryJedis)pooledJedis.getObject();if(jedis.getDB()!=(long)this.database){ jedis.select(this.database); } }publicvoiddestroyObject(PooledObjectpooledJedis)throwsException{ BinaryJedisjedis=(BinaryJedis)pooledJedis.getObject();if(jedis.isConnected()){try{try{ jedis.quit(); }catch(Exceptionvar4){ } jedis.disconnect(); }catch(Exceptionvar5){ } } }publicPooledObjectmakeObject()throwsException{ Jedisjedis=newJedis(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); }returnnewDefaultPooledObject(jedis); }publicvoidpassivateObject(PooledObjectpooledJedis)throwsException{ }publicbooleanvalidateObject(PooledObjectpooledJedis){ BinaryJedisjedis=(BinaryJedis)pooledJedis.getObject();try{returnjedis.isConnected()&&jedis.ping().equals("PONG"); }catch(Exceptionvar4){returnfalse; } } }
3.2 配置类JedisPoolConfig
publicclassJedisPoolConfigextendsGenericObjectPoolConfig{publicJedisPoolConfig(){this.setTestWhileIdle(true);this.setMinEvictableIdleTimeMillis(60000L);this.setTimeBetweenEvictionRunsMillis(30000L);this.setNumTestsPerEvictionRun(-1); } }
4 国际物流履约系统中的应用
在国际物流履约系统中,我们和客户交互文件经常使用sftp服务器,因为创建sftp服务器的连接比较耗时,所以基于Apache Commons Pool对象池的框架来实现的我们自己的sftp链接池。
4.1 sftp对象池
SftpPool比较简单,直接继承GenericObjectPool。
publicclassSftpPoolextendsGenericObjectPool{publicSftpPool(SftpFactoryfactory,SftpPoolConfigconfig,SftpAbandonedConfigabandonedConfig){super(factory,config,abandonedConfig); } }
4.2 对象工厂SftpFactory
这是基于Apache Commons Pool框架实现自定义对象池的核心类,代码如下:
publicclassSftpFactoryextendsBasePooledObjectFactory{privatestaticfinalStringCHANNEL_TYPE="sftp";privatestaticPropertiessshConfig=newProperties();privateStringhost;privateintport;privateStringusername;privateStringpassword;static{ sshConfig.put("StrictHostKeyChecking","no"); }@Override publicSftpcreate(){try{ JSchjsch=newJSch(); SessionsshSession=jsch.getSession(username,host,port); sshSession.setPassword(password); sshSession.setConfig(sshConfig); sshSession.connect(); ChannelSftpchannel=(ChannelSftp)sshSession.openChannel(CHANNEL_TYPE); channel.connect(); log.info("sftpFactory创建sftp");returnnewSftp(channel); }catch(JSchExceptione){ log.error("连接sftp失败:",e);thrownewBizException(ResultCodeEnum.SFTP_EXCEPTION); } }/** *@paramsftp被包装的对象 *@return对象包装器 */ @Override publicPooledObjectwrap(Sftpsftp){returnnewDefaultPooledObject<>(sftp); }/** *销毁对象 *@paramp对象包装器 */ @Override publicvoiddestroyObject(PooledObjectp){ log.info("开始销毁channelSftp");if(p!=null){ Sftpsftp=p.getObject();if(sftp!=null){ ChannelSftpchannelSftp=sftp.getChannelSftp();if(channelSftp!=null){ channelSftp.disconnect(); log.info("销毁channelSftp成功"); } } } }/** *检查连接是否可用 * *@paramp对象包装器 *@return{@codetrue}可用,{@codefalse}不可用 */ @Override publicbooleanvalidateObject(PooledObjectp){if(p!=null){ Sftpsftp=p.getObject();if(sftp!=null){try{ sftp.getChannelSftp().cd("./"); log.info("验证连接是否可用,结果为true");returntrue; }catch(SftpExceptione){ log.info("验证连接是否可用,结果为false",e);returnfalse; } } } log.info("验证连接是否可用,结果为false");returnfalse; }publicstaticclassBuilder{privateStringhost;privateintport;privateStringusername;privateStringpassword;publicSftpFactorybuild(){returnnewSftpFactory(host,port,username,password); }publicBuilderhost(Stringhost){this.host=host;returnthis; }publicBuilderport(intport){this.port=port;returnthis; }publicBuilderusername(Stringusername){this.username=username;returnthis; }publicBuilderpassword(Stringpassword){this.password=password;returnthis; } } }
4.3 配置类SftpPoolConfig
配置类继承了GenericObjectPoolConfig,可继承该类的默认属性,也可自定义配置参数。
publicclassSftpPoolConfigextendsGenericObjectPoolConfig{publicstaticclassBuilder{privateintmaxTotal;privateintmaxIdle;privateintminIdle;privatebooleanlifo;privatebooleanfairness;privatelongmaxWaitMillis;privatelongminEvictableIdleTimeMillis;privatelongevictorShutdownTimeoutMillis;privatelongsoftMinEvictableIdleTimeMillis;privateintnumTestsPerEvictionRun;privateEvictionPolicyevictionPolicy;//仅2.6.0版本commons-pool2需要设置 privateStringevictionPolicyClassName;privatebooleantestOnCreate;privatebooleantestOnBorrow;privatebooleantestOnReturn;privatebooleantestWhileIdle;privatelongtimeBetweenEvictionRunsMillis;privatebooleanblockWhenExhausted;privatebooleanjmxEnabled;privateStringjmxNamePrefix;privateStringjmxNameBase;publicSftpPoolConfigbuild(){ SftpPoolConfigconfig=newSftpPoolConfig(); 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);returnconfig; } }
4.4 SftpClient配置类
读取配置文件,创建SftpFactory、SftpPoolConfig、SftpPool,代码如下:
@Configuration @ConditionalOnClass(SftpPool.class)@EnableConfigurationProperties(SftpClientProperties.class)publicclassSftpClientAutoConfiguration{ @Bean @ConditionalOnMissingBean publicISftpClientsftpClient(SftpClientPropertiessftpClientProperties){if(sftpClientProperties.isMultiple()){ MultipleSftpClientmultipleSftpClient=newMultipleSftpClient(); sftpClientProperties.getClients().forEach((name,properties)->{ SftpFactorysftpFactory=createSftpFactory(properties); SftpPoolConfigsftpPoolConfig=createSftpPoolConfig(properties); SftpAbandonedConfigsftpAbandonedConfig=createSftpAbandonedConfig(properties); SftpPoolsftpPool=newSftpPool(sftpFactory,sftpPoolConfig,sftpAbandonedConfig); ISftpClientsftpClient=newSftpClient(sftpPool); multipleSftpClient.put(name,sftpClient); });returnmultipleSftpClient; } SftpFactorysftpFactory=createSftpFactory(sftpClientProperties); SftpPoolConfigsftpPoolConfig=createSftpPoolConfig(sftpClientProperties); SftpAbandonedConfigsftpAbandonedConfig=createSftpAbandonedConfig(sftpClientProperties); SftpPoolsftpPool=newSftpPool(sftpFactory,sftpPoolConfig,sftpAbandonedConfig);returnnewSftpClient(sftpPool); } publicSftpFactorycreateSftpFactory(SftpClientPropertiesproperties){returnnewSftpFactory.Builder() .host(properties.getHost()) .port(properties.getPort()) .username(properties.getUsername()) .password(properties.getPassword()) .build(); } publicSftpPoolConfigcreateSftpPoolConfig(SftpClientPropertiesproperties){ SftpClientProperties.Poolpool=properties.getPool();returnnewSftpPoolConfig.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(); } publicSftpAbandonedConfigcreateSftpAbandonedConfig(SftpClientPropertiesproperties){ SftpClientProperties.Abandonedabandoned=properties.getAbandoned();returnnewSftpAbandonedConfig.Builder() .removeAbandonedOnBorrow(abandoned.isRemoveAbandonedOnBorrow()) .removeAbandonedOnMaintenance(abandoned.isRemoveAbandonedOnMaintenance()) .removeAbandonedTimeout(abandoned.getRemoveAbandonedTimeout()) .logAbandoned(abandoned.isLogAbandoned()) .requireFullStackTrace(abandoned.isRequireFullStackTrace()) .logWriter(newPrintWriter(System.out)) .useUsageTracking(abandoned.isUseUsageTracking()) .build(); } }
4.5 对象SftpClient
SftpClient是实际工作的类,从SftpClient 中可获取到一个sftp链接,使用完成后,归还给sftpPool。SftpClient代码如下:
publicclassSftpClientimplementsISftpClient{privateSftpPoolsftpPool;/** *从sftp连接池获取连接并执行操作 * *@paramhandlersftp操作 */ @Override publicvoidopen(ISftpClient.Handlerhandler){ Sftpsftp=null;try{ sftp=sftpPool.borrowObject(); ISftpClient.HandlerpolicyHandler=newDelegateHandler(handler); policyHandler.doHandle(sftp); }catch(Exceptione){ log.error("sftp异常:",e);thrownewBizException(ResultCodeEnum.SFTP_EXCEPTION); }finally{if(sftp!=null){ sftpPool.returnObject(sftp); } } }@AllArgsConstructor staticclassDelegateHandlerimplementsISftpClient.Handler{privateISftpClient.Handlertarget;@Override publicvoiddoHandle(Sftpsftp){try{ target.doHandle(sftp); }catch(Exceptione){ log.error("sftp异常:",e);thrownewBizException(ResultCodeEnum.SFTP_EXCEPTION); } } } }
4.6 实战代码示例
通过sftp上传文件到XX服务器
//通过SFTP上传到XX((MultipleSftpClient)sftpClient).choose("XX"); sftpClient.open(sftp->{ booleanexist=sftp.isExist(inventoryPath);if(!exist){ sftp.mkdirs(inventoryPath); }//执行sftp操作 InputStreamis=newFileInputStream(oneColumnCSVFile); sftp.upload(inventoryPath,titleName,is); log.info("inventoryuploadover"); });
5 总结
通过本文的介绍可以知道,Apache Commons Pool定义了一个对象池的行为,提供了可扩展的配置类和对象工厂,封装了对象创建、从池中获取对象、归还对象的核心流程。还介绍了开源框架Jedis是如何基于GenericObjectPool来实现的连接池。最后介绍了国际物流履约系统中是如何基于GenericObjectPool来管理Sftp连接的。
掌握了GenericObjectPool的核心原理,我们就可以通过实现几个关键的接口,创建一个对象池管理工具,在项目中避免了对象的频繁创建和销毁,从而显著提升程序的性能。
标签: