Search code examples
springspring-aopspring-annotations

CommonsPoolTargetSource doesn't create objects


I am trying to implement the CommonsPoolTargetSource but I can't figure out what I need to do to get it to create my minimum objects at start? By reading the docs what I have in my app configuration should be the what I need to get started but tests show that the pool is at zero instead of the minimum I set which is 4.

Here is my App Configuration:

@Configuration
@PropertySource({"classpath:config.properties"})
@ComponentScan(basePackages = {"com.mf.bb"})
public class AppConfig {
    @Bean
    public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:i18n/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setCacheSeconds(0);
        return messageSource;
    }

    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceHolderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }

    @Bean
    @Scope("prototype")
    public BBIDSApp bbIDSApp() {
        return new BBIDSApp();
    }

    @Bean
    public CommonsPoolTargetSource commonsPoolTargetSource() {      
        CommonsPoolTargetSource commonsPoolTargetSource = new CommonsPoolTargetSource();
        commonsPoolTargetSource.setMinIdle(4);
        commonsPoolTargetSource.setMaxSize(50);
        commonsPoolTargetSource.setTargetBeanName("bbIDSApp");
        commonsPoolTargetSource.setTargetClass(BBIDSApp.class);
        System.err.println("I'm alive!!!");
        return commonsPoolTargetSource;
    }

    @Bean
    public ProxyFactoryBean proxyFactoryBean() {
        ProxyFactoryBean proxyFactoryBean = new ProxyFactoryBean();
        proxyFactoryBean.setTargetSource(commonsPoolTargetSource());
        return proxyFactoryBean;
    }

    @Bean
    public MethodInvokingFactoryBean poolConfigAdvisor() {
        MethodInvokingFactoryBean poolConfigAdvisor = new MethodInvokingFactoryBean();
        poolConfigAdvisor.setTargetObject(commonsPoolTargetSource());
        poolConfigAdvisor.setTargetMethod("getMaxIdle");
        return poolConfigAdvisor;
    }

}

Solution

  • This is working as expected, or better said it is working but not as you would expect it to. Not Spring's CommonsPoolTargetSource nor the normal Commons Pool's GenericObjectPool don't initialize the number of objects in pool by simply creating the pool object.

    In Spring, CommonsPoolTargetSource creates the pool by calling new GenericObjectPool(this). The same can be done with plain simple GenericObjectPool: GenericObjectPool pool = new GenericObjectPool(new MyBeanFactory()); and in the constructor there is no initialization logic, so objects will not be created by default when the pool instance is created.

    With plain old vanilla Commons Pool GenericObjectPool you can initialize the minIdle objects like this:

    • manually:

      GenericObjectPool<MyBean> pool = new GenericObjectPool<MyBean>(new MyBeanFactory());
      pool.setMinIdle(4);
      
      for (int i = 0; i < 4; i++) {
          pool.addObject();
      }
      
    • somewhat automatic, but ugly:

      GenericObjectPool<MyBean> pool = new GenericObjectPool<MyBean>(new MyBeanFactory());
      pool.setMinIdle(4);
      
      pool.setTimeBetweenEvictionRunsMillis(1000);
      Thread.currentThread().sleep(2000);
      

    With Spring's CommonsPoolTargetSource you don't have access to the pool instance or the addObject() method. So, you can either use the sleep() method (which is ugly) together with timeBetweenEvictionRunsMillis or let the normal implementation to size the pool automatically when needed (objects are created when needed and added to the pool).

    There would be another, third approach, of a custom CommonsPoolTargetSource that should include a init method:

    public class CustomCommonsPoolTargetSource extends CommonsPoolTargetSource {
        public void initializeMinIdleObjects() throws Exception {
            List<BBIDSApp> apps = new ArrayList<BBIDSApp>();
    
            for(int i = 0; i < getMinIdle(); i++) {
                apps.add((BBIDSApp) this.getTarget());
            }
    
            for(BBIDSApp app : apps) {
                this.releaseTarget(app);
            }
    
            apps.clear();
        }
    }
    

    and then in your AppConfig:

    @Bean(initMethod="initializeMinIdleObjects")
    public CommonsPoolTargetSource commonsPoolTargetSource() {      
        CommonsPoolTargetSource commonsPoolTargetSource = new CustomCommonsPoolTargetSource();
        commonsPoolTargetSource.setMinIdle(4);
        commonsPoolTargetSource.setMaxSize(50);
        commonsPoolTargetSource.setTargetBeanName("bbIDSApp");
        commonsPoolTargetSource.setTargetClass(BBIDSApp.class);
        System.err.println("I'm alive!!!");
        return commonsPoolTargetSource;
    }