0

これが複数のスレッドから呼び出されていると仮定すると、次のテスト メソッドはスレッド セーフですか? println ステートメントで「null」が表示されることがあります。

アイデアは、提供された識別子に基づいてオンデマンドで Bean を作成するマップを返すことです。これは、同じアプローチを使用して 1 つの 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 からの洞察に満ちたフィードバックを考慮して、アプリケーション コンテキストを直接使用する別のアプローチを試みましたが、それでも失敗し、null になります。今回は、より適切と思われる Map の代わりに Function 抽象化を使用しています。

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 チケットを送信しました。

https://jira.springsource.org/browse/SPR-9852

4

2 に答える 2

2

あなたがそれを実装した方法はお勧めしません:

  1. ジョーダンが言ったように、マップは実際には必要ありません。ハッシュマップとして使用するのではなく、.value() メソッドを呼び出すために使用するだけです。

  2. Spring @Configuration メカニズムはバイパスされています。内部的に Spring は @Configuration クラスの CGLIB プロキシを作成し、これを使用して、どの依存関係をどこに注入する必要があるかを認識し、スコープを管理する方法を知っているインスタンスを作成します。それをバイパスすることで、基本的に Spring を使用してBean インスタンスを管理できなくなります。

以下は、実装したものと同様のことを行いますが、よりシンプルで、毎回一貫して機能すると思います-これは、アプリケーションコンテキストを使用してプロトタイプ 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);
        }

    }
}
于 2012-10-03T12:53:52.127 に答える
0

あなたがしているのは get メソッドのみで不変のマップを返すことだけなので、これがスレッドセーフにならない唯一の方法は、渡された obj の値を変更する場合です。Collections.unmodifiableMap の代わりに ConcurrentHashMap を使用する方が良い解決策かもしれませんが、これは後でマップに値を追加する予定がある場合にのみ役立ちます。

于 2012-10-03T00:19:24.737 に答える