假设从多个线程调用以下测试方法,它是否是线程安全的?有时 println 语句显示“null”。
这个想法是返回一个映射,该映射将根据提供的标识符按需创建 bean。请注意,这只是一个简单的示例,用于说明类似的现实生活场景,其中一个 Bean 使用相同的方法未满足其依赖关系(例如,value.x 为 null)。对于奖励积分,是否有另一种(更好)的方法来达到相同的效果?
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.oanda.bi.rm.test.AnnotationConfigTest.Config;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { Config.class }, loader = AnnotationConfigContextLoader.class)
public class AnnotationConfigTest {
@Resource
Map<String, Value> map;
@Test
public void test() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool( 10 );
for ( int i = 0; i < 10; i++ ) {
service.execute( new Runnable() {
@Override
public void run() {
// Is this thread-safe?
Value value = map.get( "value" );
// Sometimes null!
System.out.println( value.x );
}
} );
}
service.shutdown();
service.awaitTermination( 1, TimeUnit.MINUTES );
}
public static class Value {
@Resource
protected Integer x;
}
@Configuration
public static class Config {
@Bean
public Integer x() {
return 1;
}
@Bean
@Scope("prototype")
public Value value() {
return new Value();
}
@Bean
@SuppressWarnings("serial")
public Map<String, Value> map() {
// Return a Spring agnostic "bean factory" map
return Collections.unmodifiableMap( new HashMap<String, Value>() {
@Override
public Value get( Object obj ) {
String key = (String) obj;
if ( key.equals( "value" ) ) {
// Create new bean on demand
return value();
}
// Assume other similar branches here...
return null;
}
} );
}
}
}
更新
鉴于 Biju Kunjummen 的富有洞察力的反馈,我尝试了一种直接使用应用程序上下文的不同方法,但它仍然失败。这次我使用 Function 抽象而不是 Map ,这似乎更合适:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import com.google.common.base.Function;
import com.oanda.bi.rm.test.AnnotationFunctionConfigTest.Config;
/**
* Unit test that tries to perform the same pattern as {#link ResourceConfig} and ensure
* thread safety.
*
* @see http://stackoverflow.com/questions/12700239/thread-safety-of-calling-bean-methods-from-returned-annonymous-inner-classes/12700284#comment17146235_12700284
* @see http://forum.springsource.org/showthread.php?130731-Thread-safety-of-calling-Bean-methods-from-returned-annonymous-inner-classes&p=426403#post426403
* @author btiernay
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { Config.class }, loader = AnnotationConfigContextLoader.class)
public class AnnotationFunctionConfigTest {
@Resource
Function<String, Value> function;
@Test
public void test() throws InterruptedException {
final int threads = 10;
ExecutorService service = Executors.newFixedThreadPool( threads );
for ( int i = 0; i < threads; i++ ) {
service.execute( new Runnable() {
@Override
public void run() {
Value value = function.apply( "value" );
Assert.assertNotNull( value.x );
}
} );
}
service.shutdown();
service.awaitTermination( 1, TimeUnit.MINUTES );
}
public static class Value {
@Resource
protected Integer x;
}
@Configuration
public static class Config {
@Bean
public Integer x() {
return 1;
}
@Bean
@Scope("prototype")
public Value value() {
return new Value();
}
@Bean
public Function<String, Value> function() {
// Return a Spring agnostic "bean factory" function
return new Function<String, Value>() {
@Autowired
private ApplicationContext applicationContext;
@Override
public Value apply( String key ) {
if ( key.equals( "value" ) ) {
// Create new bean on demand
return applicationContext.getBean( key, Value.class );
}
// Assume other similar branches here...
return null;
}
};
}
}
}
有人愿意评论一下为什么这仍然看起来不安全吗?
更新
这看起来可能是一个 Spring 错误。我提交了 jira 票证:
最佳答案
我不推荐您实现它的方式:
就像 Jordan 所说,那里并不真正需要映射,您根本不将其用作 HashMap ,而只是使用它来调用 .value() 方法。
Spring @Configuration 机制被绕过,Spring 在内部为 @Configuration 类创建一个 CGLIB 代理,并使用它知道哪些依赖项需要注入(inject)到哪里,并创建知道如何管理范围的实例,通过绕过它你基本上不再使用 Spring 来管理您的 bean 实例。
以下内容与您实现的内容类似,但我认为更简单并且每次都能一致地工作 - 这是通过使用应用程序上下文来获取原型(prototype) bean 并以这种方式将其隐藏在自定义工厂后面:
更新实现
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class AnnotationConfigTest {
@Autowired PrototypeBeanFactory prototypeFactory;
@Test
public void test() throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool( 10 );
for ( int i = 0; i < 10; i++ ) {
service.execute( new Runnable() {
@Override
public void run() {
Value value = prototypeFactory.getBean("value", Value.class);
System.out.println( "value1.x = " + value.getX() );
}
} );
}
service.shutdown();
service.awaitTermination( 1, TimeUnit.MINUTES );
}
public static class Value {
@Autowired
private Integer x;
public Integer getX() {
return x;
}
public void setX(Integer x) {
this.x = x;
}
}
@Configuration
public static class Config {
@Bean
public Integer x() {
return 1;
}
@Bean
@Scope(value="prototype")
public Value value() {
return new Value();
}
@Bean
public PrototypeBeanFactory prototypeFactory(){
return new PrototypeBeanFactory();
}
}
public static class PrototypeBeanFactory implements ApplicationContextAware{
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public<T> T getBean(String name, Class<T> clazz){
return this.applicationContext.getBean(name, clazz);
}
}
}
关于java - 从返回的匿名内部类调用 @Bean 方法的线程安全性,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12700239/