ActiveMQ實現動態添加消費者

版本:5.3.2

官方文檔地址:http://activemq.apache.org/components/classic/documentation

模式:主題模式(發佈訂閱)

官方給出的topic模式的訂閱者有兩種方式:

1. 註解方式:

指定destination 以及ListenerContainerFactory,示例如下:

@Service
public class TopicConsumer {
    @JmsListener(destination = "demo.topic", containerFactory = "jmsListenerContainerFactory")
    public void receiver1(User text) {
        System.out.println("TopicConsumer : receiver1 : " + text);
    }

 

2. 實現JmsListenerConfigurer

public class ActiveConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
    SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
    endpoint.setDestination("demo.topic");
    endpoint.setMessageListener(new TopicListener());
    endpoint.setId("myJmsEndpoint");
    
    registrar.setContainerFactory(jmsListenerContainerTopic);
    registrar.registerEndpoint(endpoint);
}
}

 

同樣需要設置監聽容器和監聽者,請注意,發佈訂閱模式需要自己初始化jmsListenerContainerFactory

@Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactory(ConnectionFactory connectionFactory) {
    DefaultJmsListenerContainerFactory bean = new DefaultJmsListenerContainerFactory();
    bean.setPubSubDomain(true);
    bean.setConnectionFactory(connectionFactory);
    bean.setMessageConverter(jacksonJmsMessageConverter());
    return bean;
}

 

問題:以上兩種方案的消費者都是需要事先約定好監聽的destination ,那可不可以在程序啓動後根據條件動態加載消費者呢?

 原理分析:

我們先來看下第二種實現JmsListenerConfigurer接口是怎麼加載消費者的。

@FunctionalInterface
public interface JmsListenerConfigurer {
   void configureJmsListeners(JmsListenerEndpointRegistrar registrar);
}

傳給我們一個JmsListenerEndpointRegistrar 對象,而它的作用就是一個幫助類,用於JmsListenerEndpointRegistry 註冊 JmsListenerEndpoint,JmsListenerEndpoint就是消費者。

而通過斷點我們可以知道configureJmsListeners這個方法是由JmsListenerAnnotationBeanPostProcessor 的afterSingletonsInstantiated 方法觸發的。

看下這個方法主要做了什麼:已經刪除相關性不大的代碼

public void afterSingletonsInstantiated() {
      // 1.找到所有JmsListenerConfigurer實現類,調用configureJmsListeners方法
      Map<String, JmsListenerConfigurer> beans =
            ((ListableBeanFactory) this.beanFactory).getBeansOfType(JmsListenerConfigurer.class);
      List<JmsListenerConfigurer> configurers = new ArrayList<>(beans.values());
      AnnotationAwareOrderComparator.sort(configurers);
      for (JmsListenerConfigurer configurer : configurers) {
      // 2. 現在registrar 裏有所有的消費者了。
         configurer.configureJmsListeners(this.registrar);
   }


   if (this.registrar.getEndpointRegistry() == null) {
      // 3. 從ioc容器中找到 JmsListenerEndpointRegistry bean ,並且設置給幫助類:registrar
      if (this.endpointRegistry == null) {
         Assert.state(this.beanFactory != null, "BeanFactory must be set to find endpoint registry by bean name");
         this.endpointRegistry = this.beanFactory.getBean(
               JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME, JmsListenerEndpointRegistry.class);
      }
      this.registrar.setEndpointRegistry(this.endpointRegistry);
   }


   // 4. 註冊所有的監聽者
   this.registrar.afterPropertiesSet();

 

上面的第4步尤其關鍵:註冊所有的監聽者,最後會調用 JmsListenerEndpointRegistryregisterListenerContainer 方法。

public void registerListenerContainer(JmsListenerEndpoint endpoint, JmsListenerContainerFactory<?> factory,
      boolean startImmediately) {
   synchronized (this.listenerContainers) {
      if (this.listenerContainers.containsKey(id)) {
         throw new IllegalStateException("Another endpoint is already registered with id '" + id + "'");
      }
      // 根據之前設置的監聽工廠和消費之,創建一個消息容器
      MessageListenerContainer container = createListenerContainer(endpoint, factory);
      this.listenerContainers.put(id, container);
      // 啓動容器
      if (startImmediately) {
         startIfNecessary(container);
      }
   }
}

也就是上面的這個方法是最爲核心的,即 創建容器,啓動容器。

如果需要動態的添加消費者,那麼就可以調用這個方法便能夠達到目的了。

 實現

核心原理在於:通過拿到 JmsListenerEndpointRegistry 對象,調用registerListenerContainer方法。

點進這個註解 @EnableJms 

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({JmsBootstrapConfiguration.class})
public @interface EnableJms {
}

 發現這個註解會加載JmsBootstrapConfiguration 配置類

@Configuration
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public class JmsBootstrapConfiguration {


   @Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   public JmsListenerAnnotationBeanPostProcessor jmsListenerAnnotationProcessor() {
      return new JmsListenerAnnotationBeanPostProcessor();
   }


   @Bean(name = JmsListenerConfigUtils.JMS_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
   public JmsListenerEndpointRegistry defaultJmsListenerEndpointRegistry() {
      return new JmsListenerEndpointRegistry();
   }

結果發現,JmsListenerEndpointRegistry 被ioc 管理起來了,也就是說,可以自動裝配了。

因此,下面的代碼示例展示如何動態添加消費者的能力。

發起請求 http://ip:port/regist 請求就可以動態初始化消費者了


@RestController
public class Controller  {


    @Autowired
    private JmsListenerEndpointRegistry endpointRegistry;
    @Autowired
    private JmsListenerContainerFactory containerFactory;


    @GetMapping("/regist")
    public void run() throws NoSuchFieldException, IllegalAccessException {
            SimpleJmsListenerEndpoint endpoint2 = new SimpleJmsListenerEndpoint();
        endpoint2.setId("myJmsEndpoint3");
        endpoint2.setDestination("demo.topic");
        endpoint2.setMessageListener(new TopicListener());
        // 3. 啓動容器
        registry.registerListenerContainer(endpoint2, containerFactory, true);
   

 registry.registerListenerContainer 這個方法可以多次調用,它底層是個list 把所有的消費者存起來的。

再貼下消息消費者代碼:

public class TopicListener implements MessageListener {
    @Override
    public void onMessage(Message message) {
        if (message instanceof ActiveMQTextMessage) {
            try {
                ActiveMQTextMessage txtMsg = (ActiveMQTextMessage) message;
                String messageStr = txtMsg.getText();
                System.out.println("主題監聽器 接收到文本消息:" + messageStr);
            } catch (JMSException | IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
        } else {
            throw new IllegalArgumentException("只支持 ActiveMQTextMessage 類型消息!");
        }
    }
}

 

參考文檔:

https://github.com/nilshartmann/osgi-jms-example

https://blog.csdn.net/qq_29278623/article/details/90028696?utm_medium=distribute.pc_relevant_bbs_down.none-task--2~all~first_rank_v2~rank_v29-6.nonecase&depth_1-utm_source=distribute.pc_relevant_bbs_down.none-task--2~all~first_rank_v2~rank_v29-6.nonecase

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章