背景
最近在寫一個Mqtt訊息轉發的中介軟體,可通過ActvieMq接收訊息。需要對外提供配置介面,通過配置介面,動態配置Queue,可接收Queue的訊息。
整個專案依賴於SpringBoot ,通過SpringBoot實現佇列消費,只需要通過@JmsListener(destination = “queueName”) 註解,就可以實現對特定佇列的消費。
遇到問題
正式這種註解的方式,使得destination只能在程式碼中寫死,沒法動態修改。 而我想實現的效果是能夠動態的修改destination,動態的建立Consumer。
解決方案
- 繼續使用@JmsListener,找到某種方式,能夠動態修改destination
- 不使用@JmsListener,通過ActiveMq提供的Jar,手動實現連線、消費等。之前通過這種方式實現過RabbitMq的處理,因此該方案不會有難度,只是工作量多一些。
由於SpringBoot的過分簡單,因此開始嘗試通過第一種方式。
解決過程
一開始想著通過反射修改註解destination但是嘗試失敗。後來想到@JmsListener(destination = “${xxx}”)這種方式,根據配置檔案,可以修改消費的佇列,但是需要從新啟動。 因此能不能動態修改配置檔案對應的變數,然後消費者動態注入Spring。
- 自定義實現一個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);