Spring Bean自定义作用域JMS

6
我正在使用Spring Framework和DefaultMessageListenerContainer并发地从JMS队列中消费消息。我希望能够为每个进来的消息创建自动装配的bean的新实例。我尝试过将scope="prototype",但似乎无法实现我的要求。请问是否有一种自定义的bean作用域,能够类似于HTTP请求中的“request”作用域,为每个JMS消息创建新的实例?
我知道可以让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>

我发现你的帖子对我很有帮助。请解释为什么prototype不起作用,以及为什么你想要每个JMS消息一个单独的bean。 - VB_
2个回答

4
很容易编写一个自定义范围来实现这一点...
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);
    }

}

and a sample context...

<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也放入范围中(如上面的bar和baz)。您可以使所有引用的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通知中完成,以保持监听器的整洁。

1
当然,最好使用无状态的bean来避免像这样做事情。 - Gary Russell
@GaryRussell 如果被创建的bean具有@Autowired依赖项,您的解决方案如何工作?这就是我发现所有自定义范围解决方案都存在缺陷的地方,因为它们演示了在生产中几乎不存在的基本POJO的创建。您的解决方案比大多数解决方案更好,因为您实现了BeanFactoryPostProcessor,尽管我认为出于责任分离的考虑,最好拥有单独的类。 - Abhijit Sarkar
不,我指的是链接指向的<aop:scoped-proxy/>。每次引用自动装配字段时,都会调用您自定义范围的get()方法来根据某些条件查找实例。 - Gary Russell
我知道我可以查找依赖项(虽然不确定如何,作用域不是bean,也许我需要在实例化期间传递应用程序上下文?)。我不明白的是,如果那些被标记为@Autowired的依赖关系如何注入到我的bean中?或者你是说自定义作用域的bean不应该有@Autowired依赖项? 我不想抢占这个帖子,如果你愿意,我们可以在我的问题上继续讨论。 - Abhijit Sarkar
显示剩余2条评论


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接