Suppose you're writing a set of java.io.Reader
utilities, and would like to
provide a method for dumping the contents of a Reader
to a String
.
Here's the code for the ReaderUtil
, implemented without an ObjectPool
:
import java.io.Reader; import java.io.IOException; public class ReaderUtil { public ReaderUtil() { } /** * Dumps the contents of the {@link Reader} to a * String, closing the {@link Reader} when done. */ public String readToString(Reader in) throws IOException { StringBuffer buf = new StringBuffer(); try { for(int c = in.read(); c != -1; c = in.read()) { buf.append((char)c); } return buf.toString(); } catch(IOException e) { throw e; } finally { try { in.close(); } catch(Exception e) { // ignored } } } }
For the sake of this example, let's assume we want to to pool the StringBuffer
s
used to buffer the Reader
's contents. (A pool of StringBuffer
s
may or may not be useful in practice. We're just using it as a simple example here.)
Let's further assume that a complete pool implementation will be provided via
a constructor. (We'll show you how to create such an implementation in just a moment.)
Then to use the pool we simply call borrowObject
to obtain the buffer, and
then call returnObject
when we're done with it.
Then a ReaderUtil
implementation using a pool of StringBuffer
s might look
like this:
import org.apache.commons.pool.ObjectPool; import java.io.Reader; import java.io.IOException; public class ReaderUtil { private ObjectPool pool; public ReaderUtil(ObjectPool pool) { this.pool = pool; } /** * Dumps the contents of the {@link Reader} to a * String, closing the {@link Reader} when done. */ public String readToString(Reader in) throws IOException { StringBuffer buf = null; try { buf = (StringBuffer)(pool.borrowObject()); for(int c = in.read(); c != -1; c = in.read()) { buf.append((char)c); } return buf.toString(); } catch(IOException e) { throw e; } catch(Exception e) { throw new RuntimeException("Unable to borrow buffer from pool" + e.toString()); } finally { try { in.close(); } catch(Exception e) { // ignored } try { if(null != buf) { pool.returnObject(buf); } } catch(Exception e) { // ignored } } } }
Since we've constrained ourselves to the ObjectPool
interface, an arbitrary pool
implementation (returning, in our case, StringBuffer
s) can be used. When a different
or "better" pool implemenatation comes along, we can simply drop it into our ReaderUtil
without changing a line of code.
Recall that Pool provides a simple toolkit for creating object pools. The
PoolableObjectFactory
interface is an important part of this toolkit.
PoolableObjectFactory
defines lifecycle methods for pooled objects.
We can use it to separate the kinds of objects that are pooled and how they are
created, persisted, or destroyed, from the pooling algorithm itself.
Suppose we have an ObjectPool
implementation that accepts a
PoolableObjectFactory
(for example, any of the implementations in the
org.apache.commons.pool.impl
package). Then we need only provide
the factory implemenation in order to pool a new kind of object.
Here's a PoolableObjectFactory
implementation that creates
StringBuffer
s as used above.
import org.apache.commons.pool.BasePoolableObjectFactory; public class StringBufferFactory extends BasePoolableObjectFactory { // for makeObject we'll simply return a new buffer public Object makeObject() { return new StringBuffer(); } // when an object is returned to the pool, // we'll clear it out public void passivateObject(Object obj) { StringBuffer buf = (StringBuffer)obj; buf.setLength(0); } // for all other methods, the no-op // implementation in BasePoolableObjectFactory // will suffice }
We can, for example, use this factory with the StackObjectPool
to instantiate our
ReaderUtil
as follows:
new ReaderUtil(new StackObjectPool(new StringBufferFactory()))