2
votes

Update: Posted the non-working version on https://github.com/justsomecoder/boot-rabbit-issue

I am trying to implement some basic RabbitMQ sending and receiving functionality but cannot seem to get it working across both my Spring Boot projects. While I have tried multiple examples, as of now I implemented the example at https://spring.io/guides/gs/messaging-rabbitmq/, with the only exception that I defined the beans in the Application.java in a separate RabbitConfig class annotated with @Configuration.

I also tried it the exact way it's done in the example, however this also does not work.

The funny thing is, the implementation works in one (older) Spring Boot project while it's not working in a newer Boot project. In the other project the output during runtime shows me that the connection to RabbitMQ is succesfully setup:

Part of output working project

2017-12-18 10:53:45,205 INFO [restartedMain] o.s.j.e.a.AnnotationMBeanExporter [MBeanExporter.java:431] Registering beans for JMX exposure on startup
2017-12-18 10:53:45,215 INFO [restartedMain] o.s.j.e.a.AnnotationMBeanExporter [MBeanExporter.java:916] Bean with name 'rabbitConnectionFactory' has been autodetected for JMX exposure
2017-12-18 10:53:45,219 INFO [restartedMain] o.s.j.e.a.AnnotationMBeanExporter [MBeanExporter.java:678] Located managed bean 'rabbitConnectionFactory': registering with JMX server as MBean [org.springframework.amqp.rabbit.connection:name=rabbitConnectionFactory,type=CachingConnectionFactory]
2017-12-18 10:53:45,238 INFO [restartedMain] o.s.c.s.DefaultLifecycleProcessor [DefaultLifecycleProcessor.java:343] Starting beans in phase -2147482648
2017-12-18 10:53:45,239 INFO [restartedMain] o.s.c.s.DefaultLifecycleProcessor [DefaultLifecycleProcessor.java:343] Starting beans in phase 2147483647
2017-12-18 10:53:45,268 INFO [container-1] o.s.a.r.c.CachingConnectionFactory [AbstractConnectionFactory.java:359] Created new connection: SimpleConnection@6bb6caa0 [delegate=amqp://[email protected]:32770/, localPort= 51889]
2017-12-18 10:53:45,273 INFO [container-1] o.s.a.r.c.RabbitAdmin [RabbitAdmin.java:491] Auto-declaring a non-durable, auto-delete, or exclusive Queue (floors) durable:false, auto-delete:false, exclusive:false. It will be redeclared if the broker stops and is restarted while the connection factory is alive, but all messages will be lost.

Output non-working project

2017-12-18 18:40:39.452  INFO 4722 --- [  restartedMain] name.nameFootstepProcessor   : Starting nameFootstepProcessor on Macbook-Pro-van-Lars.local with PID 4722 (/Users/lars/IdeaProjects/name-footstep-processor/target/classes started by lars in /Users/lars/IdeaProjects/name-footstep-processor)
2017-12-18 18:40:39.453  INFO 4722 --- [  restartedMain] name.nameFootstepProcessor   : No active profile set, falling back to default profiles: default
2017-12-18 18:40:39.453 DEBUG 4722 --- [  restartedMain] o.s.boot.SpringApplication               : Loading source class name.nameFootstepProcessor
2017-12-18 18:40:39.945  INFO 4722 --- [  restartedMain] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@426b0d00: startup date [Mon Dec 18 18:40:39 CET 2017]; root of context hierarchy
2017-12-18 18:40:40.697  INFO 4722 --- [  restartedMain] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
2017-12-18 18:40:41.389  INFO 4722 --- [  restartedMain] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration' of type [org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration$$EnhancerBySpringCGLIB$$b410db6c] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

It also shows up as a consumer for the queue in the RabbitMQ Admin Management interface.

In my other project, none of this is shown. However, in both projects the Rabbit configuration class is found and the beans are loaded (checked using Print all the Spring beans that are loaded - Spring Boot).

Both projects share the same dependency 'spring-boot-starter-amqp'. They also both share the same application.properties file containing the right information for connecting to my local RabbitMQ server. Is there anything I can do to find out why one project is loading the Rabbit configuration beans correctly while the other is not?

Below I have attached some files which I think are helpful to understand the problem more, but if any other files or output are needed please let me know. I changed some package names for privacy concerns.

Thank you!

Regards, larsl95.

Working project Spring Boot 1.5.3

Non-working project Spring Boot 1.5.9

application.properties (same for both projects, port is due to Docker, mapped to 5276)

spring.rabbitmq.host=localhost
spring.rabbitmq.port=32770
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

RabbitConfig.java (same for both projects)

package name.configuration;

import org.springframework.context.ConfigurableApplicationContext;
import name.queue.Receiver;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Arrays;

@Component
@Configuration
public class RabbitConfig {

    public final static String queueName = "floors";

    public RabbitConfig(ConfigurableApplicationContext ctx) {
        this.printBeans(ctx);
    }

    @Bean
    Queue queue() {
        return new Queue(queueName, false);
    }

    @Bean
    TopicExchange exchange() {
        return new TopicExchange("test-exchange");
    }

    @Bean
    Binding binding(Queue queue, TopicExchange exchange) {
        return BindingBuilder.bind(queue).to(exchange).with(queueName);
    }

    @Bean
    SimpleMessageListenerContainer container(ConnectionFactory connectionFactory,
                                             MessageListenerAdapter listenerAdapter) {
        SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
        container.setConnectionFactory(connectionFactory);
        container.setQueueNames(queueName);
        container.setMessageListener(listenerAdapter);
        return container;
    }

    @Bean
    MessageListenerAdapter listenerAdapter(Receiver receiver) {
        return new MessageListenerAdapter(receiver, "receiveMessage");
    }

    // just added this to know which beans are loaded, called it from constructor to see
    // if RabbitConfig class is found by Spring at all
    private void printBeans(ConfigurableApplicationContext ctx) {
        String[] beanNames = ctx.getBeanDefinitionNames();
        Arrays.sort(beanNames);
        for (String beanName : beanNames) {
            System.out.println(beanName);
        }
    }

}

Receiver.java (same for both projects)

package name.queue;

import org.springframework.stereotype.Component;

import java.util.concurrent.CountDownLatch;

@Component
public class Receiver {

    private CountDownLatch latch = new CountDownLatch(1);

    public void receiveMessage(String message) {
        System.out.println("Received <" + message + ">");
        latch.countDown();
    }

    public CountDownLatch getLatch() {
        return latch;
    }
}

Main.java (non-working project)

@SpringBootApplication
public class Main {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(Main.class);
        app.setWebEnvironment(false);
        ConfigurableApplicationContext appContext = app.run(args);
        name.tcp.TcpServer tcpServer = new name.tcp.TcpServer();
    }

}

Main.java (working project)

package name;

import name.tcp.TcpServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.support.SpringBootServletInitializer;
import org.springframework.context.ConfigurableApplicationContext;

import java.io.IOException;

@SpringBootApplication
public class Main extends SpringBootServletInitializer {

    public static void main(String[] args) {
        ConfigurableApplicationContext app = SpringApplication.run(Main.class, args);
        TcpServer tcpServer = new TcpServer();
        System.out.println("starting TCP server from main");
    }

}

Runner.java (same for both projects)

package name.queue;

import java.util.concurrent.TimeUnit;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Component;
import name.configuration.RabbitConfig;

@Component
public class Runner implements CommandLineRunner {

    private final RabbitTemplate rabbitTemplate;

    @Autowired
    public Runner(RabbitTemplate rabbitTemplate) {
        this.rabbitTemplate = rabbitTemplate;
    }

    @Override
    public void run(String... args) throws Exception {
        System.out.println("Sending message...");
        rabbitTemplate.convertAndSend(RabbitConfig.queueName, "Hello from RabbitMQ!");
    }

}
1
Since the story is really about Spring Boot, would you mind preparing the simplest version of non-working situation and sharing with us though GitHub? We will pull your project locally and will play to figure out the bottleneck. ThanksArtem Bilan
I will do that! I will rename some packages before I push it to GitHub and then share the link here in a bit.larsl95
Is the main in the non-working project in the same package hierarchy as the @Configuration? You show the package for the working one but not for the non-working one. Boot only looks for configuration in the main and child packages by default.Gary Russell
No it is not, however I tried both ways since I also saw this mentioned in another Stack Overflow reply. In the working project it also seems to pick up the RabbitConfig from the configuration package. I posted the non-working project here (with some renaming and removal of implementation details): github.com/justsomecoder/boot-rabbit-issue In this version it's also not in the same package hierarchy but even when moving it it still does not work.larsl95
Removing the TcpServer class made your app connect to the RabbitMQ server. I guess the while (true) does not help :)Tome

1 Answers

1
votes

Seeing the project you linked that reproduced the issue, when the application is started, the TcpServer component gets initialized, which causes the startListen() method to be executed.

That method has an infinite loop without any separate thread being created, hence blocking the remaining of the startup process (code excerpt below).

public void startListen() throws IOException {
    while (true) {
        Socket clientSocket = serverSocket.accept();
        threadPool.submit(new XClientHandler(clientSocket,eventProducer));
    }
}

Removing that component shows that the RabbitMQ listener gets started, so I would just advise to (at least) use a different Thread for your TcpServer.