我按照此答案中的说明配置了 CommonsPool2TargetSource 。然后我添加了借用验证(因为在我的真实场景中,每个池化对象都代表一个可能不再存在的进程)。
它可以工作,但是在池对象上的每个方法调用都会调用一次验证。每个对我们服务的请求可能会多次(通常只有两次)。(如果我记录池对象,再加上一次toString
。)但是,每个请求一次验证就足够了。
我怎样才能更好地控制它?例如,要验证
- 仅在
ApplicationContext.getBean
(在链接答案中使用)或Provider.get
(我在下面使用的更多 DI 方式)上, - 或仅当池对象的方法抛出“断管”IOException;然后用一个新的替换它并重试,透明的。
使用的版本:spring-aop-5.2.5.RELEASE、Apache commons-pool2-2.7.0
代码(主要从这个答案复制)
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 = "fooTarget";
/**
* @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("2") int maxSize
) {
final AbstractPoolingTargetSource poolingConfig = new CommonsPool2TargetSource() {
@Override
protected GenericObjectPool<?> createObjectPool()
{
GenericObjectPool<?> pool = (GenericObjectPool<?>) super.createObjectPool();
pool.setTestOnBorrow(true);
return pool;
}
@Override
public boolean validateObject(PooledObject<Object> 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;
}
}
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("foo")
private Provider<Foo> fooProvider;
@Test
public void test() {
final Runnable runnable = () -> {
Foo foo = fooProvider.get();
// Does not help - getSingletonTarget returns null
// foo = Optional.ofNullable((Foo) AopProxyUtils.getSingletonTarget(foo)).orElse(foo);
LOG.debug("foo == {}", 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();
}
}
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(">>>>>>>>>>> Called instance {}", instanceNumber);
try {
Thread.sleep(SLEEP_PERIOD);
} catch (InterruptedException e) {
LOG.error(e.getMessage(), e);
}
}
public boolean validate() {
LOG.info("({}).validate() at {}", this, Stream.of(new Throwable().getStackTrace()).limit(9).map(s -> s.getMethodName()).collect(Collectors.joining(" < ")));
return true;
}
}
显示由 toString 引起的验证调用的输出
main |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < toString < lambda$0 < test
Thread-4 |INFO |(test.commonspool.inject.Foo@4624aeba).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < toString < lambda$0 < run
Thread-2 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < toString < lambda$0 < run
Thread-4 |INFO |(test.commonspool.inject.Foo@4624aeba).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < call < lambda$0 < run
Thread-2 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < call < lambda$0 < run
Thread-4 |WARN |>>>>>>>>>>> Called instance 1
Thread-2 |WARN |>>>>>>>>>>> Called instance 2
Thread-3 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < toString < lambda$0 < run
main |INFO |(test.commonspool.inject.Foo@4624aeba).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < call < lambda$0 < test
main |WARN |>>>>>>>>>>> Called instance 1
Thread-3 |INFO |(test.commonspool.inject.Foo@91da29b).validate() at validate < validateObject < borrowObject < borrowObject < getTarget < intercept < call < lambda$0 < run
Thread-3 |WARN |>>>>>>>>>>> Called instance 2