Search code examples
javaspringamqpspring-amqp

Spring AMQP - anonymous queue declaration


I am trying to declare anonymous queue using Spring AMQP annotations, but seems it is not working.

@RabbitListener(
  id = "b1",
  bindings =
      @QueueBinding(
          value =
              @Queue(
                  name = "",
                  durable = "false",
                  exclusive = "true",
                  autoDelete = "true",
                  admins = "amqpAdmin1"),
          exchange =
              @Exchange(
                  value = "${my.rabbitmq[0].exchange}", declare = "false",
                  ignoreDeclarationExceptions = "true",
                  type = ExchangeTypes.TOPIC,
                  admins = "amqpAdmin1"),
          key = "*.*.*.*.*.*.*.-",
          admins = "amqpAdmin1"),
  admin = "amqpAdmin1",
  containerFactory = "lf1",
  autoStartup = "true")

I read that I can use empty name param, and it should create anonymous queue - but the thing is that I need server-generated name (I have no privileges to name my own queue). Is this possible at all with spring AMQP lib currently?

I see in the code that it currently uses

private String declareQueue(org.springframework.amqp.rabbit.annotation.Queue bindingQueue,
        Collection<Declarable> declarables) {
    String queueName = (String) resolveExpression(bindingQueue.value());
    boolean isAnonymous = false;
    if (!StringUtils.hasText(queueName)) {
        queueName = Base64UrlNamingStrategy.DEFAULT.generateName(); //<---- HERE
        // default exclusive/autodelete and non-durable when anonymous
        isAnonymous = true;
    }

but here instead of Base64UrlNamingStrategy.DEFAULT.generateName(); -> I would need to spring to go to RabbitMQ broker and fetch RabbitMQ generated name and use that one.

UPDATE

@Bean
  public Queue q2(){
    Queue q = QueueBuilder.nonDurable().exclusive().autoDelete().build();
    //Queue q = new Queue("", false, true, true); --> same
    q.setAdminsThatShouldDeclare(amqpAdmin2());
    return q;
  }

  @Bean
  public Exchange e2(@Value("${my.rabbitmq[1].exchange}") String name){
    Exchange e = ExchangeBuilder.topicExchange(name).ignoreDeclarationExceptions().build();
    e.setAdminsThatShouldDeclare(amqpAdmin2());
    return e;
  }

  @Bean
  public Binding bin2(Queue q2, Exchange e2){
    Binding b = BindingBuilder.bind(q2).to(e2).with("*.*.*.*.*.*.*.-").noargs();
    b.setAdminsThatShouldDeclare(amqpAdmin2());
    return b;
  }

  @RabbitListener(id = "b2", queues = "#{q2}",
      admin = "amqpAdmin2",
      containerFactory = "lf2", autoStartup = "true")
  public void listen2(GenericMessage<?> msg) {
    comparator.recordForComparison(props.getRabbitmq().get(1).getId(), msg.getPayload());
  }

and this is in the logs:

Auto-declaring a non-durable, auto-delete, or exclusive Queue () durable:false, auto-delete:true, exclusive:true. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.
2022-02-10 09:22:43.660  INFO 30076 --- [           main] o.s.a.r.l.DirectMessageListenerContainer : Container initialized for queues: [spring.gen-ZAVana-pSSW2WaanrkVZXw_awaiting_declaration]
2022-02-10 09:22:43.661  INFO 30076 --- [           main] o.s.a.r.c.CachingConnectionFactory       : Attempting to connect to: my.server.com:5671
2022-02-10 09:22:43.765 ERROR 30076 --- [           b2-1] o.s.a.r.l.DirectMessageListenerContainer : Queue not present, scheduling consumer SimpleConsumer [queue=spring.gen-ZAVana-pSSW2WaanrkVZXw_awaiting_declaration, index=0, consumerTag=null identity=3f844994] for restart

and this is what I added after removing @RabbitListener. Now it works with this:

@Bean
  public MessageListenerContainer c2(DirectRabbitListenerContainerFactory lf2, Queue q2){
    DirectMessageListenerContainer lc = lf2.createListenerContainer();
    lc.setQueues(q2);
    lc.setAutoStartup(true);
    MessageListenerAdapter a = new MessageListenerAdapter();
    a.setMessageConverter(converter());
    a.setDefaultListenerMethod("receive");
    a.setDelegate(new MessageReceiver(props.getRabbitmq().get(1).getId()));
    lc.setupMessageListener(a);
    lc.afterPropertiesSet();
    return lc;
  }

class MessageReceiver{

    private final int id;

    public MessageReceiver(int id){
      this.id= id;
    }

    void receive(Object o){
      comparator.recordForComparison(this.id, o);
    }
  }

Solution

  • I found the problem - the @RabbitListener bean postprocessor grabs the name before it has been declared.

    Here is a work around (manually inject the queue):

    @SpringBootApplication
    public class So71033589Application {
    
        public static void main(String[] args) {
            SpringApplication.run(So71033589Application.class, args);
        }
    
        @RabbitListener(id = "foo", autoStartup = "false")
        public void listen(String in) {
            System.out.println(in);
        }
    
    }
    
    @Configuration
    class Config{
    
        @Bean
        Queue q() {
            return new Queue("", false, true, true);
        }
    
        @Bean
        ApplicationRunner runner(RabbitListenerEndpointRegistry registry, Queue q) {
            return args -> {
                MessageListenerContainer container = registry.getListenerContainer("foo");
                ((AbstractMessageListenerContainer) container).setQueues(q);
                container.start();
            };
        }
    
    }