如何使用 Spring Boot 收听动态目的地?

How to listen to dynamic destinations using Spring Boot?

我们有一个应用程序使用 Spring Boot 及其 JMS 工具。在运行时,我们有不同的生产者在线跳转并告诉我们的应用程序要收听的主题或队列的名称。现在,我们有:

@JmsListener(destination = "helloworld.q")
public void receive(String message) {
  LOGGER.info("received message='{}'", message);
}

当我们向 helloworld.q 主题发送消息时,它会起作用。问题是,直到运行时我们才知道主题的名称是什么,JmsListener 似乎想要一个常量表达式。

消息生产者将挂接到我们的 ActiveMQ 实例并广播一条消息,告诉我们需要开始收听他们的主题,例如 "Wasabi"、"WhitePaper"、"SatelliteMajor"、"BigBoosters", 等等。在运行时无法知道我们需要开始收听哪些主题。

我已阅读 Spring 文档,其中解释了如何在运行时(某种程度上)收听 topics/queues:

@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...
}

我已将它作为测试推入我们的 Receiver 配置中,当我们发送消息时它确实会被调用。问题是,Spring 使所有这些东西都被自动调用,我们不知道在哪里以及如何为这个方法提供端点需要监听的 topic/queue 的名称。此外,消息侦听器似乎永远不会被调用,但这是一个单独的问题;如果我们至少可以发送自定义主题或队列让它收听,我相信我们可以解决它。

我们正在使用 Spring 2.x.

您可以使用 属性 占位符作为目的地名称

@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");
    }

}

然后设置属性,例如在 application.yml 中用于 Spring 引导应用程序,或在 command-line 属性 中启动 JVM

-Dfoo.bar=baz

编辑

您可以将侦听器 bean 制作为原型并调整环境 属性。

@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);
        }

    }
}