问题
We have an application using Spring Boot and its JMS facility. At runtime, we have different producers that jump online and tell our application the name of the topic or queue to listen to. Right now, we have:
@JmsListener(destination = "helloworld.q")
public void receive(String message) {
LOGGER.info("received message='{}'", message);
}
which works when we send a message to the helloworld.q
topic. The problem is, we won't know what the name of the topic will be until runtime, and JmsListener
seems to want a constant expression.
Message producers will hook into our ActiveMQ instance and broadcast a message telling us we need to start listening to their topic, such as "Wasabi", "WhitePaper", "SatelliteMajor", "BigBoosters", etc. There is no way to know at runtime which topics we'll need to start listening to.
I've read the Spring documentation that explains how to listen to topics/queues at runtime (sort of):
@Configuration
@EnableJms
public class ReceiverConfig implements JmsListenerConfigurer {
@Override
public void configureJmsListeners(JmsListenerEndpointRegistrar registrar) {
SimpleJmsListenerEndpoint endpoint = new SimpleJmsListenerEndpoint();
endpoint.setId("myJmsEndpoint");
endpoint.setDestination("anotherQueue");
endpoint.setMessageListener(message -> {
// processing
});
registrar.registerEndpoint(endpoint);
}
// other methods...
}
I've shoved that into our Receiver config as a test, and it does get called when we send a message. The problem is, Spring makes all this stuff get called automagically and we don't know where and how to give this method the name of the topic/queue the endpoint needs to listen to. Also, the message listener never seems to get called, but that's a separate problem; I'm sure we can solve it if we at least can send the custom topic or queue for it to listen to.
We're using Spring 2.x.
回答1:
You can use a property placeholder for the destination name
@SpringBootApplication
public class So56226984Application {
public static void main(String[] args) {
SpringApplication.run(So56226984Application.class, args);
}
@JmsListener(destination = "${foo.bar}")
public void listen(String in) {
System.out.println(in);
}
@Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> template.convertAndSend("baz", "qux");
}
}
Then set the property, e.g. in application.yml for a Spring Boot app, or a command-line property when launching the JVM
-Dfoo.bar=baz
EDIT
You can make the listener bean a prototype and adjust an environment property.
@SpringBootApplication
public class So56226984Application {
public static void main(String[] args) {
SpringApplication.run(So56226984Application.class, args).close();
}
@Bean
public ApplicationRunner runner(JmsTemplate template, JmsListenerEndpointRegistry registry,
ConfigurableApplicationContext context) {
return args -> {
Scanner scanner = new Scanner(System.in);
String queue = scanner.nextLine();
Properties props = new Properties();
context.getEnvironment().getPropertySources().addLast(new PropertiesPropertySource("queues", props));
while (!"quit".equals(queue)) {
System.out.println("Adding " + queue);
props.put("queue.name", queue);
context.getBean("listener", Listener.class);
template.convertAndSend(queue, "qux sent to " + queue);
System.out.println("There are now " + registry.getListenerContainers().size() + " containers");
queue = scanner.nextLine();
}
scanner.close();
};
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Listener listener() {
return new Listener();
}
public static class Listener {
@JmsListener(destination = "${queue.name}")
public void listen(String in) {
System.out.println(in);
}
}
}
来源:https://stackoverflow.com/questions/56226984/how-to-listen-to-dynamic-destinations-using-spring-boot