Spring Bean Custom Scope JMS
Asked Answered
P

2

6

I am using Spring Framework to concurrently consume messages off of a JMS queue using a DefaultMessageListenerContainer. I want the ability to create new instances of the beans that are autowired for each message that comes in. I thought setting the scope="prototype" would work but it doesn't seem to do the job. Does anybody know of a custom bean scope that would create new instances per JMS message? Much like the "request" scope does for HTTP Requests?

I realize that I could make com.sample.TestListener "BeanFactoryAware" and then just do a getBean("foo") in my onMessage but I wanted to avoid putting the Spring dependency into my code.

Thanks in advance for any help!

Example below, I want a new instances of "com.sample.Foo" and all beans injected into it each time a message comes in.

<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>
Physical answered 14/3, 2013 at 17:5 Comment(1)
I find you post hrlpfull for me. Please explain why prototypedoesn't work, and why you want a sepate bean per jms message.Roup
A
4

It's pretty easy to write a custom scope to do this...

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>

Note: with this simple scope, you have to put all the referenced beans in the scope as well (bar and baz above). You can make all the referenced beans inherit the scope, but it takes some work. That said - there's an example of how to do it in spring-batch's StepScope.

Note#2 this will get a new instance for every method call. If you call multiple methods you'll get a new bean for each call. If you want to scope it to allow all calls within onMessage to use the same instance, you'll need to add some more tricks.

EDIT: Here are some updates to support multiple calls to an instance within the 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();
}

Now, you do have to clear the cache...

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

But even that could be done in an AOP @After advice, to keep the listener totally clean.

Ambrosane answered 14/3, 2013 at 19:8 Comment(7)
Of course, it's better to use stateless beans to avoid having to do things like this.Ambrosane
Thanks for the answer and sample code! It is greatly appreciated.Physical
@GaryRussell How'd your solution work if the bean being created had @Autowired dependencies? This is where I find all custom scope solutions falling short because they demonstrate creating a basic POJO almost non-existent in production. Your solution is better that most because you implement BeanFactoryPostProcessor, even though I think for separation of responsibility it might be better to have separate classes.Multicolored
See the Spring Documentation about scoped beans as dependencies.Ambrosane
@GaryRussell Are you referring to this part? Customizing beans using a BeanPostProcessor? Here is my use case.Multicolored
No; I'm referring to the <aop:scoped-proxy/> which is what the link points to. Each time the autowired field is referenced, your custom scope's get() method is called to lookup the instance based on some criteria.Ambrosane
I understand I can look up the dependencies (though unsure how, a scope isn't a bean, perhaps I need to pass application context during instantiation?). What I don't understand is how to inject those dependencies into my bean if those're marked @Autowired? Or are you saying the custom scoped bean shouldn't have @Autowired dependencies? I don't want to hijack this thread, we can continue this discussion on my question if you want.Multicolored
A
0

Use SimpleThreadScope implementation which come with Spring

http://docs.spring.io/spring/docs/3.0.x/api/org/springframework/context/support/SimpleThreadScope.html

Appendage answered 23/2, 2014 at 2:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.