roblovelock
roblovelock

Reputation: 1981

Send RabbitMQ message on Spring Application shutdown

I have a monitor service that sends a message to RabbitMQ on application startup, application shutdown and every minute (tick).

The startup and tick events work fine. When the class was originally written the shutdown event also worked.

I am using spring-boot-starter-amqp 1.3.3.RELEASE

The event was fired on the destroy method of the DisposableBean interface.

I have also tried implementing ApplicationListener<ContextClosedEvent> interface and Lifecycle interface

Both the above methods return:

java.lang.IllegalStateException: The ApplicationContext is closed and the ConnectionFactory can no longer create connections.

I notice there was a bug fix https://jira.spring.io/browse/AMQP-536 which suggests the Lifecycle interface.

How do I ensure my shutdown event message is sent before the RabbitMQ connection is closed?

EDIT: More info and Latest Code

The application has multiple connection factories to different servers. The Monitor Service connects to the RabbitMQ server via monitorRabbitTemplate.

The issue seems to be the monitorRabbitTemplate connection Factory gets the Lifecycle/Shutdown/Dispose event before the MonitorService.

Latest code (using Lifecycle instead of ApplicationListener<ContextClosedEvent> and DisposableBean):

@Component
public class MonitorServiceImpl implements  MonitorService , Lifecycle {
    private static final Logger LOGGER = LoggerFactory.getLogger(MonitorServiceImpl.class);

    private final RabbitTemplate monitorRabbitTemplate;
    private final String queueName;
    private final Gson gson = new Gson();

    @Autowired
    public MonitorServiceImpl(@Qualifier("monitorRabbitTemplate") final RabbitTemplate monitorRabbitTemplate,
                              @Value("${monitor.rabbitmq.queue.name:monitor}") final String queueName) {
        this.monitorRabbitTemplate = monitorRabbitTemplate;
        this.queueName = queueName;
    }

    @Scheduled(fixedDelay = 60000)
    public void tick() {
        try {
            send(new Monitor(Status.INFO, "I am here"));
        } catch (final Exception e) {
            LOGGER.error("FAILED TO SEND TICK EVENT", e);
        }
    }

    @Override
    public void send(final Monitor monitor) {
        try {
            final Message message = MessageBuilder.withBody(gson.toJson(monitor).getBytes())
                .setContentType("application/json").setPriority(0).setDeliveryMode(MessageDeliveryMode.PERSISTENT)
                .build();

            monitorRabbitTemplate.send(queueName, message);
        } catch (final Exception e) {
            LOGGER.error("FAILED TO SEND MONITOR EVENT", e);
            LOGGER.error("FAILED TO SEND MONITOR EVENT to {}:{}", monitorRabbitTemplate.getConnectionFactory()
                .getHost(), monitorRabbitTemplate.getConnectionFactory().getPort());

        }

    }

    @Override
    public void start() {
        try {
            send(new Monitor(Status.STARTING, "Application starting up..."));
        } catch (final Exception e) {
            LOGGER.error("FAILED TO SEND STARTUP EVENT", e);
        }
    }

    @Override
    public void stop() {
        try {
            send(new Monitor(Status.TERMINATING, "Application shutdown..."));
        } catch (final Exception e) {
            LOGGER.error("FAILED TO SEND SHUTDOWN EVENT", e);
        }

    }

    @Override
    public boolean isRunning() {
        return true;
    }
}

Upvotes: 5

Views: 2802

Answers (1)

Miloš Milivojević
Miloš Milivojević

Reputation: 5369

Maybe you could share some code/build configuration? Because listening to ContextClosedEvent and implementing Lifecycle both work for me.

Here's my application:

@SpringBootApplication
public class SpringRabbitmqDemoApplication {

    @Component
    public static class Whatever implements Lifecycle {
        private final RabbitTemplate template;

        @Autowired
        public Whatever(RabbitTemplate template) {
            this.template = template;
        }

        @EventListener
        public void event(ContextClosedEvent event) throws Exception {
            sendMessage(event.toString());
        }

        private void sendMessage(String message) {
            template.convertAndSend("", "queue", message);
            System.out.println("Sent event " + message);
        }

        @Override
        public void start() {
            sendMessage("start");
        }

        @Override
        public void stop() {
            sendMessage("stop");
        }

        @Override
        public boolean isRunning() {
            return true;
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(SpringRabbitmqDemoApplication.class, args);
    }
}

And my pom.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>spring-rabbitmq-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>spring-rabbitmq-demo</name>
    <description>Demo project for Spring Boot RabbitMQ</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.3.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

Here's the output I get:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.3.3.RELEASE)

2016-07-21 17:45:24.984  INFO 77845 --- [           main] c.example.SpringRabbitmqDemoApplication  : Starting SpringRabbitmqDemoApplication on mmilivojevic-hal9000 with PID 77845 (started by mmilivojevic in /Volumes/Macintosh HD/springrabbitmqdemo)
2016-07-21 17:45:24.990  INFO 77845 --- [           main] c.example.SpringRabbitmqDemoApplication  : No active profile set, falling back to default profiles: default
2016-07-21 17:45:25.092  INFO 77845 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@27d415d9: startup date [Thu Jul 21 17:45:25 CEST 2016]; root of context hierarchy
2016-07-21 17:45:26.746  INFO 77845 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration' of type [class org.springframework.amqp.rabbit.annotation.RabbitBootstrapConfiguration$$EnhancerBySpringCGLIB$$2cf55fc7] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2016-07-21 17:45:27.620  INFO 77845 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2016-07-21 17:45:27.636  INFO 77845 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase -2147482648
2016-07-21 17:45:27.637  INFO 77845 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 2147483647
2016-07-21 17:45:27.661  INFO 77845 --- [           main] c.example.SpringRabbitmqDemoApplication  : Started SpringRabbitmqDemoApplication in 3.456 seconds (JVM running for 4.288)
2016-07-21 17:45:27.662  INFO 77845 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@27d415d9: startup date [Thu Jul 21 17:45:25 CEST 2016]; root of context hierarchy
2016-07-21 17:45:27.785  INFO 77845 --- [       Thread-1] o.s.a.r.c.CachingConnectionFactory       : Created new connection: SimpleConnection@7ff53a50 [delegate=amqp://[email protected]:5672/]
Sent event org.springframework.context.event.ContextClosedEvent[source=org.springframework.context.annotation.AnnotationConfigApplicationContext@27d415d9: startup date [Thu Jul 21 17:45:25 CEST 2016]; root of context hierarchy]
2016-07-21 17:45:27.829  INFO 77845 --- [       Thread-1] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 2147483647
2016-07-21 17:45:27.830  INFO 77845 --- [       Thread-1] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 0
Sent event stop
2016-07-21 17:45:27.831  INFO 77845 --- [       Thread-1] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase -2147482648
2016-07-21 17:45:27.834  INFO 77845 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

Upvotes: 3

Related Questions