Spring Bean User Area JMS

I am using the Spring Framework for at the same time to send messages from the JMS queue using the DefaultMessageListenerContainer . I want to create new beans instances that were automatically added for each incoming message. I thought setting scope = "prototype" would work, but it doesn't seem to do the job. Does anyone know of a bean user area that would create new instances per JMS message ? As for the request area, the "request" for HTTP requests?

I understand that I could do com.sample.TestListener "BeanFactoryAware" and then just do getBean ("foo") in my onMessage, but I did not want to put the Spring dependency in my code.

Thanks in advance for your help!

The example below, I want every instance of "com.sample.Foo" and all beans to be injected into it every time a message arrives.

<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
source share
2 answers

It is very easy to write a custom scope for 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 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 must put all reference beans in scope (bar and baz above). You can make all referenced beans inherit the scope, but this takes some work. However, there is an example of how to do this in spring-batch StepScope.

Note # 2 , this will get a new instance for each method call. If you call several methods, you will get a new bean for each call. If you want to cover it in order to allow all calls inside onMessage to use the same instance, you need to add a few more tricks.

EDIT: Below are some updates to support multiple instance calls in 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 need 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 this can be done in AOP @ After consultation, so that the listener is completely cleaned.

+3
source
0
source

Source: https://habr.com/ru/post/1469304/


All Articles