5

Spring Framework を使用して、 DefaultMessageListenerContainerを使用して JMS キューからメッセージを同時に消費しています。入ってくるメッセージごとに自動配線される Bean の新しいインスタンスを作成する機能が必要です。scope="prototype" を設定するとうまくいくと思いましたが、うまくいかないようです。JMS メッセージごとに新しいインスタンスを作成するカスタム Bean スコープを知っている人はいますか? 「リクエスト」スコープが HTTP リクエストに対して行うのと同じように?

com.sample.TestListener "BeanFactoryAware" を作成してから、onMessage で getBean("foo") を実行できることに気付きましたが、コードに Spring の依存関係を入れないようにしたかったのです。

助けてくれてありがとう!

以下の例では、「com.sample.Foo」の新しいインスタンスと、メッセージが着信するたびにすべての Bean が注入されます。

<bean id="consumer"
    class="com.sample.TestListener">
    <constructor-arg ref="foo" />
</bean> 

<!--Configures the Spring Message Listen Container. Points to the Connection 
    Factory, Destination, and Consumer -->
<bean id="MessageListenerContainer"
    class="org.springframework.jms.listener.DefaultMessageListenerContainer">
    <property name="connectionFactory" ref="CachedConnectionFactory" />
    <property name="destination" ref="Topic" />
    <property name="messageListener" ref="consumer" />
    <property name="concurrency" value="10"/> 
</bean> 

<bean id="foo" class="com.sample.Foo">
    <property name="x" ref="xx" />
    <property name="y" ref="yy" /> 
    <property name="z" ref="zz" />
</bean>
4

2 に答える 2

3

これを行うためのカスタム スコープを作成するのは非常に簡単です...

public class CustomScope implements Scope, BeanFactoryPostProcessor {

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String name = "myScope";

        beanFactory.registerScope(name, this);

        Assert.state(beanFactory instanceof BeanDefinitionRegistry,
                "BeanFactory was not a BeanDefinitionRegistry, so CustomScope cannot be used.");
        BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;

        for (String beanName : beanFactory.getBeanDefinitionNames()) {
            BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
            if (name.equals(definition.getScope())) {
                BeanDefinitionHolder proxyHolder = ScopedProxyUtils.createScopedProxy(new BeanDefinitionHolder(definition, beanName), registry, false);
                registry.registerBeanDefinition(beanName, proxyHolder.getBeanDefinition());
            }
        }
    }

    @Override
    public Object get(String name, ObjectFactory<?> objectFactory) {
        return objectFactory.getObject(); // a new one every time
    }

    @Override
    public String getConversationId() {
        return null;
    }

    @Override
    public void registerDestructionCallback(String name, Runnable callback) {

    }

    @Override
    public Object remove(String name) {
        return null;
    }

    @Override
    public Object resolveContextualObject(String arg0) {
        return null;
    }

}


public class Foo implements MessageListener {

    private Bar bar;

    public void setBar(Bar bar) {
        this.bar = bar;
    }

    @Override
    public void onMessage(Message message) {
        System.out.println(bar.getId());
    }

}
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
public class FooTests {

    @Autowired
    private Foo foo;

    @Test
    public void test() {
        Message message = mock(Message.class);
        foo.onMessage(message);
        foo.onMessage(message);
    }

}

そしてサンプルコンテキスト...

<bean class="foo.CustomScope" />

<bean id="baz" class="foo.BazImpl" scope="myScope" />

<bean id="bar" class="foo.BarImpl" scope="myScope">
    <property name="baz" ref="baz" />
</bean>

<bean id="foo" class="foo.Foo">
    <property name="bar" ref="bar" />
</bean>

注:この単純なスコープでは、参照されるすべての Bean もスコープに配置する必要があります (上記の barbaz)。参照されるすべての Bean にスコープを継承させることはできますが、多少の作業が必要です。そうは言っても、spring-batch の StepScope でそれを行う方法の例があります。

注#2これにより、メソッド呼び出しごとに新しいインスタンスが取得されます。複数のメソッドを呼び出すと、呼び出しごとに新しい Bean が取得されます。onMessage 内のすべての呼び出しが同じインスタンスを使用できるようにスコープを設定する場合は、さらにいくつかのトリックを追加する必要があります。

編集: onMessage() 内のインスタンスへの複数の呼び出しをサポートするための更新がいくつかあります...

private final ThreadLocal<Map<String, Object>> holder = new ThreadLocal<Map<String, Object>>();

...

@Override
public Object get(String name, ObjectFactory<?> objectFactory) {
    Map<String, Object> cache = this.holder.get();
    if (cache == null) {
        cache = new HashMap<String, Object>();
        this.holder.set(cache);
    }
    Object object = cache.get(name);
    if (object == null) {
        object = objectFactory.getObject();
        cache.put(name, object);
    }
    return object;
}

public void clearCache() {
    this.holder.remove();
}

さて、キャッシュをクリアする必要があります...

@Override
public void onMessage(Message message) {
    try {
        System.out.println(bar.getId());
        System.out.println(bar.getId());
    }
    finally {
        this.scope.clearCache();
    }
}

しかし、リスナーを完全にクリーンに保つために、AOP @After アドバイスでそれを行うこともできます。

于 2013-03-14T19:08:21.730 に答える