How to configure CommonsPool2TargetSource in spring?
Asked Answered
P

1

9

This become pain in my neck!!! I have three queries.

1)I want to configure CommonsPool2TargetSource in my project for pooling of my custom POJO class.

What I have done so far :

MySpringBeanConfig class :

    @Configuration
    @EnableWebMvc
    @ComponentScan(basePackages = {"com.redirect.controller","com.redirect.business","com.redirect.dao.impl","com.redirect.model"})
    @EnableTransactionManagement
    @PropertySource("classpath:" + JioTUConstant.SYSTEM_PROPERTY_FILE_NAME + ".properties")
    @Import({JioTUCouchbaseConfig.class,JioTUJmsConfig.class})
    public class JioTUBeanConfig extends WebMvcConfigurerAdapter {
          private static final Logger LOGGER = Logger.getLogger(JioTUConfig.class);

          @Bean
          public CommonsPool2TargetSource poolTargetSource() {
               CommonsPool2TargetSource commonsPool2TargetSource = new CommonsPool2TargetSource();
               commonsPool2TargetSource.setTargetBeanName("jioTUURL");
               commonsPool2TargetSource.setMinIdle(5);
               commonsPool2TargetSource.setMaxIdle(5);
               commonsPool2TargetSource.setMaxSize(10);
               return commonsPool2TargetSource;
           }

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

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

My POJO class inside "com.redirect.model" package:

@Repository
@Scope(value=ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Document
public class JioTUURL{

    @Id
    private String keyword;

    @Field
    private String url;

    @Field
    private String title;

    @Field
    private String timestamp;

    @Field
    private String ip;

    @Field
    private Integer clicks;

    @Field
    private String user;

    //Getter/Setter

}

Exception I am getting :

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type [com.redirect.model.JioTUURL] is defined: expected single matching bean but found 2: jioTUURL,proxyFactoryBean

FYI I have not define any bean for JioTUURL explicitly. It is up to the @ComponentScan of spring

If I comment the following line, inside proxyFactoryBean() method of JioTUConfig.java class

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

then it is running fine with log information as below

09-08-2016 16:28:13.866|INFO |localhost-startStop-1|Bean 'poolTargetSource' of type [class org.springframework.aop.target.CommonsPool2TargetSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)|[PostProcessorRegistrationDelegate.java:328]

2)How to fetch objects from pool?

@Controller
public class JioTUController {

    private static final Logger LOGGER = Logger.getLogger(JioTUController.class);
    @Autowired
    private JioTUCommonBusiness jioTUCommonBusiness;
    @Autowired
    private ObjectFactory<JioTUURL> jioTUURLObjectFactory;//Need to replace I guess

    public JioTUController() {
         LOGGER.info("Loading JioTUController complete");
    }
    @RequestMapping(method = RequestMethod.GET, value="/*")
    public ModelAndView postDataAsJSON(HttpServletRequest request,HttpServletResponse response, ModelAndView modelAndView) {

            //Should be replace with code that fetch object for me from the pool
            JioTUURL jioTUURL = jioTUURLObjectFactory.getObject();

            //App Business
    }
}

3)Is those objects in pool recycled or is going to re-instantiate after each HTTP request served?

Petronella answered 9/8, 2016 at 12:38 Comment(0)
M
6

I've been confronted to the same problem and have reproduced a simple case that seems to be working. I suppose it is too late to help you, but I hope it might help future readers.

Configuring the pool

You have to configure three elements:

  • The original bean, that you are pooling. Naturally, you will need to specify the prototype scope.
  • The CommonsPool2TargetSource, which will require the name of the prototype bean you just configured.
  • A ProxyFactoryBean that will use the just configured TargetSource.
import org.keyboardplaying.bean.Foo;
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 = "fooTarget";

    /**
     * Returns the pooled bean.
     * 
     * @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();
    }

    /**
     * Returns the pool.
     * 
     * @return the pool
     */
    @Bean
    public TargetSource fooSource(
        // You probably would externalize this value to your application.properties
        @Value("2") int maxSize
    ) {
        final AbstractPoolingTargetSource poolingConfig = new CommonsPool2TargetSource();
        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;
    }
}

Quick test

My Foo bean is quite simple. It is associated to an ID on creation and simply logs it on logging.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.atomic.AtomicInteger;

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(">>>>>>>>>>> Called instance {}", instanceNumber);
        try {
            Thread.sleep(SLEEP_PERIOD);
        } catch (InterruptedException e) {
            LOG.error(e.getMessage(), e);
        }
    }
}

Here is a (dirty) class to run this bean several times, in parallel threads.

import org.keyboardplaying.bean.Foo;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PoolConfigurationTest {

    @Autowired
    private ApplicationContext context;

    @Test
    public void test() {
        final Runnable runnable = () -> {
            // Note: the name is required as both "foo" and "fooTarget" will match class Foo.
            final Foo foo = (Foo) context.getBean("foo");
            foo.call();
        };
        new Thread(runnable).start();
        new Thread(runnable).start();
        new Thread(runnable).start();
        runnable.run();
    }
}

If you have a look at the log below, you'll see we use only two instances (corresponding to the maxSize I set). When both instances are in use, the next threads have to wait for the previous processings to be over, hence a pause of 1 s (the sleep time of Foo) in the logs.

14:30:59.624  WARN [    main] Foo: >>>>>>>>>>> Called instance 1
14:30:59.624  WARN [Thread-4] Foo: >>>>>>>>>>> Called instance 2
14:31:00.626  WARN [Thread-5] Foo: >>>>>>>>>>> Called instance 2
14:31:00.626  WARN [Thread-3] Foo: >>>>>>>>>>> Called instance 1
Mathi answered 25/7, 2019 at 12:43 Comment(2)
Thanks, works for me. For nicer dependency injection, instead of calling getBean, the client could inject a javax.inject.Provider<Foo> (or probably use other ways desribed in Injecting Prototype Beans into a Singleton Instance in Spring).Fraktur
Forgot to mention, the autowired Provider<Foo> requires @javax.inject.Named("foo") because of the ambiguity mentioned in your getBean comment.Fraktur

© 2022 - 2024 — McMap. All rights reserved.