How to use Pooled Spring beans instead of Singleton ones?
For efficiency reasons, I am interested in limiting the number of threads that si开发者_开发技巧multaneously uses the beans of the Spring application context (I don't want an unlimited number of threads proccessing in my limited memory).
I have found here (spring documentation) a way to achieve this by pooling the beans in a EJB style, by doing the following:
- Declare the target bean as scope "prototype".
- Declare a Pool provider that will deliver a limited number of pooled "target" instances.
- Declare a "ProxyFactoryBean" which function is not clear to me.
Here is the declaration of this beans:
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="businessObject" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="targetSource" ref="poolTargetSource"/>
<property name="interceptorNames" value="myInterceptor"/>
</bean>
My problem is that when I will declare another bean to use pooled instances of the "businessObjectTarget", how should I do it? I mean, when i try to do something like this:
<bean id="clientBean" class="com.mycompany.ClientOfTheBusinessObject">
<property name="businessObject" ref="WHAT TO PUT HERE???"/>
</bean>
What should be the value of the "ref" ??
You cannot use properties to get instances of prototypes.
One option is to use the lookup methods (see chapter 3.3.7.1)
Another option to get your bean in code: make your com.mycompany.ClientOfTheBusinessObject
to implement the ApplicationContextAware
interface and then call context.getBean("clientBean")
Please note the name of the third bean in the spring example:-"businessObject"
It means this the bean from where you are supposed to access the common pool.
For your case if you need your own client bean you may have it as follows. But in such a case businessObject is not required.:-
<bean id="businessObjectTarget" class="com.mycompany.MyBusinessObject"
scope="prototype">
... properties omitted
</bean>
<bean id="poolTargetSource" class="org.springframework.aop.target.CommonsPoolTargetSource">
<property name="targetBeanName" value="businessObjectTarget"/>
<property name="maxSize" value="25"/>
</bean>
<bean id="clientBean" class="com.mycompany.ClientOfTheBusinessObject">
<property name="poolTargetSource" ref="poolTargetSource"/>
</bean>
Java classes:-
public class ClientOfTheBusinessObject{
CommonsPoolTargetSource poolTargetSource;
//<getter and setter for poolTargeTSource>
public void methodToAccessCommonPool(){
//The following line gets the object from the pool.If there is nothing left in the pool then the thread will be blocked.(The blocking can be replaced with an exception by changing the properties of the CommonsPoolTargetSource bean)
MyBusinessObject mbo = (MyBusinessObject)poolTargetSource.getTarget();
//Do whatever you want to do with mbo
//the following line puts the object back to the pool
poolTargetSource.releaseTarget(mbo);
}
}
I'm pretty sure you can limit the number of simultaneous threads in a less convoluted way. Did you look at the Java Concurrency API, specifically at the Executors.newFixedThreadPool() ?
i used java-configuration to construct a proxy over the interface that handles pooling using apache commons-pool to achieve invocation-level-pooling.
I did it using Annotations based configuration:
I did create my
BusinessObject
class as a POJO and annotate it this way:@Component("businessObject") @Scope("prototype") public class BusinessObject { ... }
I gave it a specific name and did mark it as prototype so that Spring doesn't create a singleton instance for it; every time the bean is required, Spring would create a new instance.
In my
@Configuration
class (or in the@SpringBootApplication
class, if using Spring Boot) I created aCommonsPool2TargetSource
instance to holdBusinessObject
instances:@Bean public CommonsPool2TargetSource pooledTargetSource() { final CommonsPool2TargetSource commonsPoolTargetSource = new CommonsPool2TargetSource(); commonsPoolTargetSource.setTargetBeanName("businessObject"); commonsPoolTargetSource.setTargetClass(BusinessObject.class); commonsPoolTargetSource.setMaxSize(maxPoolSize); return commonsPoolTargetSource; }
Here I'm indicating that the pool will hold
BusinessObject
instances. Notice that mymaxPoolSize=?
value is set with the max number ofBusinessObject
instances I want to hold in the pool.Finally, I did access my pooled instances this way:
@Autowired private CommonsPool2TargetSource pooledTargetSource; void someMethod() { // First I retrieve one pooled BusinessObject instance BusinessObject businessObject = (BusinessObject)pooledTargetSource.getTarget(); try { // Second, I do some logic using the BusinessObject instance gotten } catch (SomePossibleException e) { // Catch and handle any potential error, if any } finally { // Finally, after executing my business logic // I release the BusinessObject instance so that it can be reused pooledTargetSource.releaseTarget(businessObject); } }
It is very important to always make sure to release the
BusinessObject
borrowed from the pool, without mattering if the business logic did finish successfully or with error. Otherwise the pool could get empty with all the instances being borrowed and never released and any further requests for instances will block forever.
精彩评论