`

对象池化技术与Apache Pool代码分析

阅读更多

 

池化技术初探与Apache Pool代码分析

1.池化技术简介

池化技术就是把一些经常使用的,创建起来又比较费时间的对象事先创建好放在内存中,这样在用的时候就可以直接从中取出以提高程序执行效率的策略,同时使用完比的对象还可以归还到池中,减少了一部分创建对象的时间。但是这种方式会带来一定的内存开销,尤其是对于那些生成时开销不大的对象,使用池化技术反而会是性能降低。因此就需要从程序的相应时间和内存空间的消耗上找到一个最佳契合点。

对于没有状态的对象(例如String),在重复使用之前,无需进行任何处理;对于有状态的对象(例如StringBuffer),在重复使用之前,就需要把它们恢复到等同于刚刚生成时的状态。由于条件的限制,恢复某个对象的状态的操作不可能实现了的话,就得把这个对象抛弃,改用新创建的实例了。

由于在同一时刻可能会有很多个线程同时向池请求对象,因此在池化技术实现的时候要时刻考虑多线程的问题。关于多线程技术在池化技术中的应用,请参考《多线程技术在池化技术的应用》一文。

2.Apache comman pool代码研究

Apache comman pool (以下简称“Pool组件”)是一个用于在java程序中实现对象池化的组件,它只包含两个包,涉及到的类较少,类之间的关系也相对简单。要想使用Pool组件,需要获取相关jar文件并将其放入CLASSPATH即可。为了研究其内部实现,我们可以下载其源代码,这些东西都可以从http://commons.apache.org/pool/download_pool.cgi下载,也可以从本篇文章的附件中获得。

2.1 Pool组件的两个包

Pool组件包含两个包:org.apache.commons.pool和org.apache.commons.pool.imp。包org.apache.commons.pool为对象池定义了一个简单的接口,和一些在实现对象池时可能会用到的类。这个包并没有实现对象池,但是却规定了为了有更好的移植性而必须要遵循的规范。而包org.apache.commons.pool.imp则提供了几种对象池的实现。

2.2org.apache.commons.pool包详解

该包中所包含的所有类如下表所示:

所有接口

KeyedObjectPool

A "keyed" pooling interface.

KeyedObjectPoolFactory

A factory for creating KeyedObjectPools.

KeyedPoolableObjectFactory

An interface defining life-cycle methods for instances to be served by a KeyedObjectPool.

ObjectPool

A pooling interface.

ObjectPoolFactory

A factory interface for creating ObjectPools.

PoolableObjectFactory

An interface defining life-cycle methods for instances to be served by an ObjectPool.

 

所有类

BaseKeyedObjectPool

A simple base implementation of KeyedObjectPool.

BaseKeyedPoolableObjectFactory

A base implementation of KeyedPoolableObjectFactory.

BaseObjectPool

A simple base implementation of ObjectPool.

BasePoolableObjectFactory

A base implementation of PoolableObjectFactory.

PoolUtils

This class consists exclusively of static methods that operate on or return ObjectPool or KeyedObjectPool related interfaces.

从上表中可以看出,出去最后一个PoolUtils工具类外,改包中的类可以分为两种,一种是带有“Keyed”的,另一种是没有带“Keyed”的。我们暂且不管第一种,只看没有带“Keyed”的五个类,这五个类的类图如下所示:

2.2.1三个接口

我们先从三个接口:PoolableObjectFactoryObjectPoolObjectPoolFactory说起。这三个使得pool组件可以以不同的方式创建并池化对象。是该包的核心也是整个Pool组件的核心。

1.PoolableObjectFactory:可池化对象工厂。该接口定义了一个可池化对象的生命周期,即创建对象,校验对象,激活对象和挂起对象,销毁对象等五个方法。其类图如下所示:

 

实际使用的时候需要利用这个接口的一个具体实现。Pool组件本身没有包含任何一种PoolableObjectFactory实现,需要根据情况自行创立。

2. ObjectPool:对象池。这个接口定义了一个对象池必须实现的方法,例如向外部提供一个池中可用的对象,收回一个从外部返回的对象,关闭连接池等。其类图如下所示:

3.ObjectPoolFactory:对象池工厂。该接口用于生成连接池,它只定义了一个方法createPool(),其类图如下所示:

有的读者可能为问,当用到对象池时,直接New一个不就得了,为什么要通过ObjectPoolFactory这个工厂类呢?对于这个疑问,我个人的看法是这其实是一种工厂方法模式的应用,见《工厂方法模式的应用实例——创建不同的对象池》

2.2.2两个抽象类

接下来我们关注一下两个抽象类:BasePoolableObjectFactoryBaseObjectPoolPoolableObjectFactory定义了许多方法,可以适应多种不同的情况。但是,在并没有什么特殊需要的时候,直接实现PoolableObjectFactory接口,就要编写若干的不进行任何操作,或是始终返回true的方法来让编译通过,比较繁琐。这种时候就可以借助BasePoolableObjectFactory的威力,来简化编码的工作。BasePoolableObjectFactoryorg.apache.commons.pool包中的一个抽象类。它实现了PoolableObjectFactory接口,并且为除了makeObject之外的方法提供了一个基本的实现――activateObjectpassivateObjectdestroyObject不进行任何操作,而validateObject始终返回true。通过继承这个类,而不是直接实现PoolableObjectFactory接口,就可以免去编写一些只起到让编译通过的作用的代码的麻烦了。这其实是适配器模式的简略形式(详见《适配器模式的使用范例》)BaseObjectPool的功能同BasePoolableObjectFactory类似,不再赘述。键值对对象池

有时候,单用对池内所有对象一视同仁的对象池,并不能解决的问题。例如,对于一组某些参数设置不同的同类对象――比如一堆指向不同地址的java.net.URL对象或者一批代表不同语句的java.sql.PreparedStatement对象,用这样的方法池化,就有可能取出不合用的对象的麻烦。

可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。但是,如果使用普通的ObjectPool来实施这个计策的话,因为普通的PoolableObjectFactory只能生产出大批设置完全一致的对象,就需要为每一组参数相同的对象编写一个单独的PoolableObjectFactory,工作量相当可观。这种时候就适合调遣Pool组件中提供的一种带键值的对象池来展开工作了。

Pool组件采用实现了KeyedObjectPool接口的类,来充当带键值的对象池。相应的,这种对象池需要配合实现了KeyedPoolableObjectFactory接口的类和实现了KeyedObjectPoolFactory接口的类来使用(这三个接口都在org.apache.commons.pool包中定义)。三个接口中定义的方法形式如出一辙,只是每个方法都增加了一个Object key参数而已。

2.3org.apache.commons.pool.impl包详解

这个包提供了五种对象池的实现,我们选择其中其中的一种——普通对象池进行简单分析,如果想了解更加详细的实现,请参考《Apache DBCP数据库连接池的实现》。这种对象池由以下两个类实现: GenericObjectPoolGenericObjectPoolFactory

 

2.3.1GenericObjectPool

 

1. 属性

该类具有的属性有:

//对象池中处于挂起状态的最多对象数。
    private int _maxIdle = DEFAULT_MAX_IDLE;
    //对象池中处于挂起状态的最少对象数。
    private int _minIdle = DEFAULT_MIN_IDLE;
    //对象池中最多的处于可用状态的对象数。
    private int _maxActive = DEFAULT_MAX_ACTIVE;
    //当对象耗尽时的状态为“锁定”且池内对象已被耗尽时,最多等待_maxWait毫秒后将抛出异常并锁定borrowObject方法。
    private long _maxWait = DEFAULT_MAX_WAIT;
    //当池内对象被用尽之后采取的措施,默认为DEFAULT_WHEN_EXHAUSTED_ACTION(即WHEN_EXHAUSTED_BLOCK,锁定borrowObject方法)
    private byte _whenExhaustedAction = DEFAULT_WHEN_EXHAUSTED_ACTION;
    /**
     * 如果这个值为true,则当池中的某个对象被借出时将会被检验是否可用。
     * 如果不可用,这个对象将被从池中剔除,同时向外借出另一个对象。
     * 检验对象的具体方法由用户在实现PoolableObjectFactory接口的boolean validateObject(Object obj)方法中实现。
     */
    private volatile boolean _testOnBorrow = DEFAULT_TEST_ON_BORROW;
    /**
     * 如果这个值为true,则当池中的某个对象被归还时将会被检验是否可用。
     * 如果不可用,这个对象将被从池中剔除。
     * 检验对象的具体方法由用户在实现PoolableObjectFactory接口的boolean validateObject(Object obj)方法中实现。
     */
    private volatile boolean _testOnReturn = DEFAULT_TEST_ON_RETURN;
    /**
     * 如果这个值为true,则当池中的某个对象在挂起时将会被检验是否可用。
     * 如果不可用,这个对象将被从池中剔除。
     * 检验对象的具体方法由用户在实现PoolableObjectFactory接口的boolean validateObject(Object obj)方法中实现。
     */
    private boolean _testWhileIdle = DEFAULT_TEST_WHILE_IDLE;
    //两次被定时检验线程检验是否可用之间的毫秒数,如果为负数,则永远不会被检验。
private long_timeBetweenEvictionRunsMillis=DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS;
    //默认的每次被检验线程检验是否可用的对象数目。
    private int _numTestsPerEvictionRun =  DEFAULT_NUM_TESTS_PER_EVICTION_RUN;
    //池内处于闲置状态的对象被“清理”线程移除之前可以闲置在对象池内的最短时间。
    private long _minEvictableIdleTimeMillis = DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
    //在保证池内至少有“minIdle”个闲置对象的前提下,被“清理”线程移除之前可以闲置在对象池内的最短时间。
    Private long_softMinEvictableIdleTimeMillis=DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS;
    //借出对象的顺序,默认借出最近归还的一个对象。
    private boolean _lifo = DEFAULT_LIFO;
    //CursorableLinkedList是一个类似于java.util.LinckedList的数据结构,用于存数对象池中的对象。
    private CursorableLinkedList _pool = null;
    //用于记录闲置对象清理器当前的位置。
private CursorableLinkedList.Cursor _evictionCursor = null;
// 可池化对象工厂,是一个实现了PoolableObjectFactory接口的工厂类,该类根据池中所存放对象不同而不同,由用户自行实现。
private PoolableObjectFactory _factory = null;
	// 已经借出但是尚未归还的对象,这个对象正在被使用中。
	private int _numActive = 0;
	// 对象“清理器”,是一个定时执行的对象,用以移除池中该移除的对象。
	private Evictor _evictor = null;
	// 处于内部处理过程(比如被创建或者被销毁)中的对象数目,这些对象应该在总数中,但是既不处于活动状态也不属于闲置状态。
	private int _numInternalProcessing = 0;
	// 用以维护多个访问borrowObject()方法以借用池中对象的线程的访问顺序,并按照此顺寻分配线程,保证先到先得。
	private final LinkedList _allocationQueue = new LinkedList();
 

同时该类还为以上属性提供了一些默认值,如下所示:

 

// 当对象池中对象被用完时(处于活动的对象数目已达到最大值)的措施标志——抛出异常
public static final byte WHEN_EXHAUSTED_FAIL   = 0; 
//当对象池中对象被用完时(处于活动状态的对象数目已达到最大值)的措施标志——锁定borrow方法,直到有新的
可用对象生成。
public static final byte WHEN_EXHAUSTED_BLOCK  = 1; 
//  当对象池中对象被用完时(处于活动状态的对象数目已达到最大值)的措施标志——生成新的对象并返回。  
public static final byte WHEN_EXHAUSTED_GROW   = 2; 
// 默认的最大处于空闲状态的对象数目。
public static final int DEFAULT_MAX_IDLE  = 8;
// 默认的最小处于空闲状态的对象数目。
public static final int DEFAULT_MIN_IDLE = 0; 
// 默认的最大处于活动状态的对象数目。
public static final int DEFAULT_MAX_ACTIVE  = 8; 
// 默认的池内对象耗尽时的措施——锁定borrow方法。
    public static final byte DEFAULT_WHEN_EXHAUSTED_ACTION = WHEN_EXHAUSTED_BLOCK; 
// 默认的借出顺序,借出最近归还的一个对象。
    public static final boolean DEFAULT_LIFO = true; 
// 默认最长等待时间,如果超过这个时间后borrow方法任然不能返回一个对象,则抛出一个异常。
    public static final long DEFAULT_MAX_WAIT = -1L; 
// 默认在借出时不检查是否可用。
public static final boolean DEFAULT_TEST_ON_BORROW = false; 
// 默认在归还时不检查是否可用。
public static final boolean DEFAULT_TEST_ON_RETURN = false; 
// 默认在挂起时不检查是否可用。
public static final boolean DEFAULT_TEST_WHILE_IDLE = false; 
// 默认两次被定时检验线程检验是否可用之间的毫秒数,如果为负数,则永远不会被检验
    public static final long DEFAULT_TIME_BETWEEN_EVICTION_RUNS_MILLIS = -1L;
    // 默认的每次被检验线程检验是否可用的对象数目。
    public static final int DEFAULT_NUM_TESTS_PER_EVICTION_RUN = 3; 
    // 池内处于闲置状态的对象被“清理”线程移除之前可以闲置在对象池内的最短时间。
    public static final long DEFAULT_MIN_EVICTABLE_IDLE_TIME_MILLIS = 1000L * 60L * 30L;
    // 在保证池内至少有“minIdle”个闲置对象的前提下,被“清理”线程移除之前可以闲置在对象池内的最短时间。
    public static final long DEFAULT_SOFT_MIN_EVICTABLE_IDLE_TIME_MILLIS = -1; 

 2. 构造方法

这个类提供了两种构造方法,第一种是通过传值为其属性或者部分属性赋值这种普通的方式来构造对象,例如:

 

public GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, 
long maxWait,int maxIdle, int minIdle, boolean testOnBorrow, 
boolean testOnReturn, long timeBetweenEvictionRunsMillis,
                             int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, 
boolean testWhileIdle,
long softMinEvictableIdleTimeMillis, boolean lifo) {
        _factory = factory;
        _maxActive = maxActive;
        _lifo = lifo;
        switch(whenExhaustedAction) {
            case WHEN_EXHAUSTED_BLOCK:
            case WHEN_EXHAUSTED_FAIL:
            case WHEN_EXHAUSTED_GROW:
                _whenExhaustedAction = whenExhaustedAction;
                break;
            default:
                throw new IllegalArgumentException("whenExhaustedAction " + whenExhaustedAction + " not recognized.");
        }
        _maxWait = maxWait;
        _maxIdle = maxIdle;
        _minIdle = minIdle;
        _testOnBorrow = testOnBorrow;
        _testOnReturn = testOnReturn;
        _timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
        _numTestsPerEvictionRun = numTestsPerEvictionRun;
        _minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
        _softMinEvictableIdleTimeMillis = softMinEvictableIdleTimeMillis;
        _testWhileIdle = testWhileIdle;
        _pool = new CursorableLinkedList();
        startEvictor(_timeBetweenEvictionRunsMillis);
    }

 
这两个方法实现了向连接池中放入对象。
其中GenericObjectPool.Config config的作用就是把该类的属性封装起来,该内部类的源代码很简单,再次不在展示。这样可以作为一个整体传入,虽然依据《重构既有代码》中所说,这也不是一个好办法,但这避免了第一种构造法方法中超长列表的出现。

 

3. 几个重要方法

uvoid addObject()和addObjectToPool()

其核心代码如下所示:

  public void addObject() throws Exception {
 …… ……
//调用由用户实现的可池化对象工厂生成一个可池化对象
        Object obj = _factory.makeObject();
…… ……
//将该对象添加到对象池中
addObjectToPool(obj, false);
…… ……
 

uborrowObject()向访问对象池的线程返回一个对象:

 

 public Object borrowObject() throws Exception {
       …………
//根据不同的参数组合做不同的处理,由于代码逻辑比较复杂,在此不在赘述,请参看《一个多线程技术的应用实例》
                    switch(whenExhaustedAction) {
                        case WHEN_EXHAUSTED_GROW:
                             …………
                            break;
                        case WHEN_EXHAUSTED_FAIL: 
                             …………
                            break;
                        case WHEN_EXHAUSTED_BLOCK: 
                             …………
                            break; 
//最终返回一个池中的对象,lantch是一个特殊的键值对的数据结构,其中有一个数据元素用于存储其请求到的对象。
                return latch.getPair().value;
       …………
 

 

uinvalidateObject()废弃池中的对象

public void invalidateObject(Object obj) throws Exception {
             ………………
//调用池中对象对应的工厂类,废弃池中的对象,具体实现由用户完成。
                _factory.destroyObject(obj);
             ………………
}
 

ureturnObject(Object obj)() 向池中归还借出的对象addObjectToPool(Object obj, boolean decrementNumActive)

public void returnObject(Object obj)和 throws Exception {
           ………………
            addObjectToPool(obj, true);
           ………………
}
private void addObjectToPool(Object obj, boolean decrementNumActive) throws Exception {
//如果在相池中加入对象时需要检验是否可用,则先进行检验。
        boolean success = true;
        if(_testOnReturn && !(_factory.validateObject(obj))) {
            success = false;
        } else {
            _factory.passivateObject(obj);
        }
             …………
//根据设定的“先入先出”还是“先入后出”模式向池中添加对象ObjectTimestampPair是一个用于存储池中//对象的数据结构
                    if (_lifo) {
                        _pool.addFirst(new ObjectTimestampPair(obj));
                    } else {
                        _pool.addLast(new ObjectTimestampPair(obj));
                    }
             …………
  2.3.2   GenericObjectPoolFactory


该类用于生成GenericObjectPool对象,此类封装了GenericObjectPool的所有属性,并将其默认值设置为GenericObjectPool的属性的默认值,其核心方法为ObjectPool createPool(),代码如下:

public ObjectPool createPool() {
        return new GenericObjectPool(_factory,_maxActive,_whenExhaustedAction,_maxWait,_maxIdle,_minIdle,_testOnBorrow,_testOnReturn,_timeBetweenEvictionRunsMillis,_numTestsPerEvictionRun,_minEvictableIdleTimeMillis,_testWhileIdle,_softMinEvictableIdleTimeMillis,_lifo);
}
 

当然,如果不想使用默认值,我们可以将其属性设置为其他值,这样在构造同类型同参数的对象池时,直接调用上述方法就可以了。

(本文章引用了http://www.ibm.com/developerworks/cn/java/l-common-pool/index.html中的部分内容。)

  • 大小: 29 KB
  • 大小: 36.6 KB
  • 大小: 11.3 KB
分享到:
评论

相关推荐

    java对象池化技术[参照].pdf

    Java对象池化技术是一种优化程序性能的策略,它通过预先创建一组对象并保留它们,以便在需要时重复使用,而不是每次需要时都创建新的对象。这样可以显著减少对象的创建和初始化时间,尤其适用于那些创建成本较高的...

    apache common pool2 实例

    在Java应用中,对象池化是一种常用的优化策略,它通过复用已经创建的对象来减少系统资源的消耗。Apache Commons Pool2 提供了灵活的接口和配置选项,使得开发者可以根据需要定制自己的对象池。 在这个实例中,我们...

    commons-pool-1.2-src.zip

    Apache Commons Pool 是一个开源项目,提供了对象池化的实现,它是一个通用的对象池库,用于创建各种资源对象的池,如数据库连接池。标题中的"commons-pool-1.2-src.zip"表明这是一个包含Apache Commons Pool 1.2...

    commons-pool2-2.5.0-bin.zip

    Apache Commons Pool 2的核心概念是对象池化,这是一种优化技术,通过复用已创建的对象来减少对象的创建和销毁开销。在Java中,特别是在处理数据库连接(如JDBC连接)、线程池、网络套接字或其他昂贵资源时,对象池...

    commo-pool, commons-pool commons-pool commons-pool

    Apache Commons Pool 是一个Java对象池库,主要用于提供各种对象池化的实现,以便高效地管理和复用有限的资源。标题中的"commo-pool, commons-pool commons-pool commons-pool"可能是由于输入错误,正确的应该是...

    commons-pool2-sr:Apache Commons Pool原始代码剖析笔记

    2. **Pool接口与PoolImpl类**:Apache Commons Pool2中的`Pool`接口定义了对象池的基本操作,如获取、返回、检查对象等。`GenericObjectPool`或`GenericKeyedObjectPool`是其实现,提供了具体的功能,如设置最大/...

    commons-pool2-2.4.2-src

    通过研究Apache Commons Pool2的源码,开发者不仅可以学习到对象池化的概念,还能了解到如何通过代码实现和优化这种技术。对于想要深入理解Java并发编程和资源管理的开发者来说,这是一个宝贵的参考资料。

    dbcp1.4&pool1;.5.6

    总结来说,Apache Commons DBCP 1.4和Apache Commons POOL 1.5.6是两个用于优化数据库访问效率的Java工具包,它们通过对象池化技术提高了系统性能和资源利用率。结合使用这两个组件,可以构建高效、稳定的数据库连接...

    commons-pool2-2.6.0-bin.zip

    Apache Commons Pool2是一个Java对象池库,用于管理可重用对象。...通过对象池化,可以显著降低系统资源消耗,提高应用程序的运行效率。在使用DBCP进行数据库连接管理时,Apache Commons Pool2起着至关重要的作用。

    commons-poo ljar包

    Apache Commons Pool是一个开源项目,提供了一种对象池化机制,它是Java编程中实现线程池技术的重要组件。线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池可以...

    commons-pool

    标题"commons-pool"指的是Apache Commons Pool项目,这是一个开源的Java库,它提供了对象池化的实现。这个库的核心功能是提供一种机制,可以有效地管理和复用已创建的对象,避免了每次需要对象时都进行新的实例化,...

    CommonPool2Demo

    这个示例代码通常会展示如何创建一个基于Apache Commons Pool2的对象池,以及如何进行对象的获取、使用和返回。以下是一般的步骤: 1. **创建PooledObjectFactory**: 首先,你需要定义一个工厂类,实现...

    commons-pool2-2.4.2.jar

    总的来说,`commons-pool2-2.4.2.jar` 是MyBatis二级缓存与Redis集成的关键组件,通过提供对象池化服务,优化了Redis连接的管理,提高了整体系统的效率和响应速度。了解并正确配置Apache Commons Pool2,有助于...

    apache线程池

    Apache Commons Pool 1.5.3是该库的一个稳定版本,包含了源代码和API文档,便于开发者理解和自定义线程池行为。 线程池的概念源于多线程编程,其核心思想是预先创建一定数量的线程,而不是每次需要时都创建新的线程...

    commons-pool-1.5.5-src 源码

    Apache Commons Pool 是一个开源项目,由Apache软件基金会维护,它提供了一个对象池化的实现,用于高效管理资源,如数据库连接、线程或者其他昂贵创建的对象。源码版本为1.5.5,是我们今天要深入探讨的重点。 ...

    commons-pool-1.6.rar

    这个"commons-pool-1.6.rar"压缩包包含的是Apache Commons Pool 1.6版本的源代码、类库和其他相关文件。Apache Commons Pool 提供了一种高效且灵活的方式来创建、管理和复用对象,以减少对象创建和销毁的开销,提高...

    commons-pool-1.3.jar+commons-collections-3.2.1.jar

    Apache Commons Pool是Apache软件基金会的一个项目,提供了一个对象池化的实现。对象池化是一种优化资源管理的技术,通过预先创建并维护一组对象,避免频繁地创建和销毁对象带来的性能开销。在`commons-pool-1.3.jar...

    commons-pool2-2.2-bin.zip

    对象池的概念是基于池化技术,它允许开发者创建一个对象池来存储和管理一组对象,而不是每次需要时都创建新对象。这样可以显著减少内存分配和垃圾收集的开销,尤其是在频繁创建和销毁对象的应用场景中。 Apache ...

    commonsJar包(包含常用jar)

    在本案例中,提及的`commons-pool-1.6.jar`是Apache Commons中的一个组件,专门用于对象池化。 Apache Commons Pool是Java对象池设计模式的一个实现。对象池是一种设计模式,它通过预先创建并维护一组可重用的对象...

    commons-pool-1.4-src.zip

    Apache Commons Pool是Apache软件基金会的一个开源项目,它提供了一个通用的对象池实现,使得开发者可以方便地在自己的应用中使用对象池技术。这里的"commons-pool-1.4-src.zip"是一个源代码压缩包,包含了Apache ...

Global site tag (gtag.js) - Google Analytics