SpringBoot @JmsListener(destination = ) 執行時動態修改

liuhh發表於2019-01-19

背景

最近在寫一個Mqtt訊息轉發的中介軟體,可通過ActvieMq接收訊息。需要對外提供配置介面,通過配置介面,動態配置Queue,可接收Queue的訊息。

整個專案依賴於SpringBoot ,通過SpringBoot實現佇列消費,只需要通過@JmsListener(destination = “queueName”) 註解,就可以實現對特定佇列的消費。

遇到問題

正式這種註解的方式,使得destination只能在程式碼中寫死,沒法動態修改。 而我想實現的效果是能夠動態的修改destination,動態的建立Consumer。

解決方案

  1. 繼續使用@JmsListener,找到某種方式,能夠動態修改destination
  2. 不使用@JmsListener,通過ActiveMq提供的Jar,手動實現連線、消費等。之前通過這種方式實現過RabbitMq的處理,因此該方案不會有難度,只是工作量多一些。

由於SpringBoot的過分簡單,因此開始嘗試通過第一種方式。

解決過程

一開始想著通過反射修改註解destination但是嘗試失敗。後來想到@JmsListener(destination = “${xxx}”)這種方式,根據配置檔案,可以修改消費的佇列,但是需要從新啟動。 因此能不能動態修改配置檔案對應的變數,然後消費者動態注入Spring。

  1. 自定義實現一個MapPropertySource,然後加入到Environment中

@Configuration
public class DynamicTestSetting {

public static final String DYNAMIC_CONFIG = "dynamic_settting";
@Autowired
AbstractEnvironment environment;

@PostConstruct
public void init() {
    environment.getPropertySources().addFirst(new DynamicLoadPropertySource(DYNAMIC_CONFIG, null));
}

}

@Slf4j
public class DynamicLoadPropertySource extends MapPropertySource {

private static Map<String, Object> map = new ConcurrentHashMap<String, Object>(64);

private static ScheduledExecutorService scheduled = Executors.newScheduledThreadPool(1);
static {
    scheduled.scheduleAtFixedRate(new Runnable() {
        @Override
        public void run() {
            //測試:定時修改value
            //真正使用可能需要傳遞一個引數或者定時讀取配置檔案
            map.put("test", String.valueOf(System.currentTimeMillis()));
        }
    }, 1, 1, TimeUnit.SECONDS);
}

public DynamicLoadPropertySource(String name, Map<String, Object> source) {
    super(name, map);
}


@Override
public Object getProperty(String name) {
    return map.get(name);
}

}

2.消費者需要動態加入
@Slf4j
public class TestConsumer {

@JmsListener(destination = "${test}")
public void receiveQueue(BytesMessage msg) {
    //接收訊息後的處理邏輯
}

}

/*動態注入bean到spring,需要用到ApplicationContext/
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) context.getAutowireCapableBeanFactory();

        BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(TestConsumer.class);
        defaultListableBeanFactory.registerBeanDefinition("testConsumer",definitionBuilder.getBeanDefinition());

//呼叫getBeaan時Spring會建立一個TestConsumer例項,這個時候ActiveMq中會建立一個destination = “${test}”佇列,TestConsummer和其繫結消費

        TestConsumer consumer = context.getBean("testConsumer", TestConsumer.class);
        System.out.println(consumer);
        
        

動態關閉Queue的消費者

參見另一篇:https://segmentfault.com/n/13…

參考文章:https://blog.csdn.net/qq_3491…

相關文章