title: Apache Commons Pool 2 in Spring with testOnBorrow (but not on every method invocation on proxy)
body: <p>I configured CommonsPool2TargetSource as explained in <a href="https://stackoverflow.com/a/57202274/2099362">this answer</a>. Then I added validation on borrow (because in my real scenario, each pooled object represents a process which might no longer be alive).</p>
  <p>It works, but the validation is called once per method invocation on the pooled object. This can be several times (typically only twice) per request to our service. (Plus once more for <code>toString</code> if I log the pooled object.) However, one validation per request should be sufficient.</p>
  <p>How can I get more control over that? For example, to validate</p>
  <ul>
  <li>only on <code>ApplicationContext.getBean</code> (used in the linked answer) or <code>Provider.get</code> (the more DI way I am using below),</li>
  <li>or only when the pooled object's method throws a &quot;broken pipe&quot; IOException; then replace it with a new one and retry, transparently.</li>
  </ul>
  <p>Used versions: spring-aop-5.2.5.RELEASE, Apache commons-pool2-2.7.0</p>
  <p><strong>Code</strong> (mostly copied from <a href="https://stackoverflow.com/a/57202274/2099362">this answer</a>)</p>
  <pre><code>package test.commonspool.inject;
  import org.apache.commons.pool2.PooledObject;
  import org.apache.commons.pool2.impl.GenericObjectPool;
  import org.springframework.aop.TargetSource;
  import org.springframework.aop.framework.ProxyFactoryBean;
  import org.springframework.aop.target.AbstractPoolingTargetSource;
  import org.springframework.aop.target.CommonsPool2TargetSource;
  import org.springframework.beans.factory.annotation.Value;
  import org.springframework.beans.factory.config.ConfigurableBeanFactory;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.context.annotation.Scope;
  
  @Configuration
  public class PoolConfiguration {
  
      // The targetBeanName is mandatory for CommonsPool2TargetSource. Rather use a constant to avoid mistakes.
      private static final String FOO_TARGET_NAME = &quot;fooTarget&quot;;
  
      /**
       * @return the pooled bean
       */
      @Bean(FOO_TARGET_NAME)
      // Remember to make this bean a prototype
      @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
      public Foo fooTarget() {
          return new Foo();
      }
  
      /**
       * @return the pool
       */
      @Bean
      public TargetSource fooSource(
          // You probably would externalize this value to your application.properties
          @Value(&quot;2&quot;) int maxSize
      ) {
          final AbstractPoolingTargetSource poolingConfig = new CommonsPool2TargetSource() {
              @Override
              protected GenericObjectPool&lt;?&gt; createObjectPool()
              {
                  GenericObjectPool&lt;?&gt; pool = (GenericObjectPool&lt;?&gt;) super.createObjectPool();
                  pool.setTestOnBorrow(true);
                  return pool;
              }
  
              @Override
              public boolean validateObject(PooledObject&lt;Object&gt; p)
              {
                  return ((Foo) p.getObject()).validate();
              }
          };
          poolingConfig.setMaxSize(maxSize);
          // The targetBeanName is mandatory
          poolingConfig.setTargetBeanName(FOO_TARGET_NAME);
          return poolingConfig;
      }
  
      /**
       * Returns a ProxyFactoryBean that is correctly pooled.
       * @return the proxy we will call
       */
      @Bean
      public ProxyFactoryBean foo(TargetSource fooSource) {
          ProxyFactoryBean proxyBean = new ProxyFactoryBean();
          proxyBean.setTargetSource(fooSource);
          return proxyBean;
      }
  }
  </code></pre>
  <pre><code>package test.commonspool.inject;
  import java.util.Optional;
  import javax.inject.Provider;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import org.springframework.aop.framework.AopProxyUtils;
  import org.springframework.beans.factory.annotation.Autowired;
  import org.springframework.boot.test.context.SpringBootTest;
  import org.springframework.test.context.junit4.SpringRunner;
  
  @RunWith(SpringRunner.class)
  @SpringBootTest(classes = { PoolConfiguration.class })
  public class PoolConfigurationTest {
      private static final Logger LOG = LoggerFactory.getLogger(PoolConfigurationTest.class);
  
      @Autowired
      @javax.inject.Named(&quot;foo&quot;)
      private Provider&lt;Foo&gt; fooProvider;
  
      @Test
      public void test() {
          final Runnable runnable = () -&gt; {
              Foo foo = fooProvider.get();
  
              // Does not help - getSingletonTarget returns null
              // foo = Optional.ofNullable((Foo) AopProxyUtils.getSingletonTarget(foo)).orElse(foo);
  
              LOG.debug(&quot;foo == {}&quot;, foo.toString()); //always invoke toString irrespective of logging config
              foo.call();
          };
          new Thread(runnable).start();
          new Thread(runnable).start();
          new Thread(runnable).start();
          runnable.run();
      }
  }
  </code></pre>
  <pre><code>package test.commonspool.inject;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  import java.util.concurrent.atomic.AtomicInteger;
  import java.util.stream.Collectors;
  import java.util.stream.Stream;
  
  public class Foo {
      public static final long SLEEP_PERIOD = 1000L;
      private static final AtomicInteger COUNTER = new AtomicInteger();
      private static final Logger LOG = LoggerFactory.getLogger(Foo.class);
  
      private final int instanceNumber;
  
      public Foo() {
          this.instanceNumber = COUNTER.incrementAndGet();
      }
  
      public void call() {
          LOG.warn(&quot;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; Called instance {}&quot;, instanceNumber);
          try {
              Thread.sleep(SLEEP_PERIOD);
          } catch (InterruptedException e) {
              LOG.error(e.getMessage(), e);
          }
      }
  
      public boolean validate() {
          LOG.info(&quot;({}).validate() at {}&quot;, this, Stream.of(new Throwable().getStackTrace()).limit(9).map(s -&gt; s.getMethodName()).collect(Collectors.joining(&quot; &lt; &quot;)));
          return true;
      }
  }
  </code></pre>
  <p><strong>Output showing validate calls caused by toString</strong></p>
  <pre><code>main     |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; toString &lt; lambda$0 &lt; test
  Thread-4 |INFO |(test.commonspool.inject.Foo@4624aeba).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; toString &lt; lambda$0 &lt; run
  Thread-2 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; toString &lt; lambda$0 &lt; run
  Thread-4 |INFO |(test.commonspool.inject.Foo@4624aeba).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; call &lt; lambda$0 &lt; run
  Thread-2 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; call &lt; lambda$0 &lt; run
  Thread-4 |WARN |&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; Called instance 1
  Thread-2 |WARN |&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; Called instance 2
  Thread-3 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; toString &lt; lambda$0 &lt; run
  main     |INFO |(test.commonspool.inject.Foo@4624aeba).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; call &lt; lambda$0 &lt; test
  main     |WARN |&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; Called instance 1
  Thread-3 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate &lt; validateObject &lt; borrowObject &lt; borrowObject &lt; getTarget &lt; intercept &lt; call &lt; lambda$0 &lt; run
  Thread-3 |WARN |&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt;&gt; Called instance 2
  </code></pre>