Spring Bean Custom Scope JMS

前端 未结 2 437
南旧
南旧 2021-01-18 14:51

I am using Spring Framework to concurrently consume messages off of a JMS queue using a DefaultMessageListenerContainer. I want the abilit

相关标签:
2条回答
  • 2021-01-18 15:24

    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.

    0 讨论(0)
  • 2021-01-18 15:24

    Use SimpleThreadScope implementation which come with Spring

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

    0 讨论(0)
提交回复
热议问题