Overview

This article introduces Spring 5.x using Spring AMQP (spring-rabbit) to integrate with RabbitMQ, declaring queues/exchanges/bindings through XML configuration, and completing send/receive loop.

Core Points:

  • RabbitAdmin负责资源自动声明
  • RabbitTemplate/AmqpTemplate负责收发
  • Direct exchange needs to explicitly specify exchange + routingKey to avoid default exchange misdelivery

Core Components

1. RabbitAdmin

Used to declare and manage RabbitMQ resources (queues, exchanges, bindings, etc.). When application starts, RabbitAdmin automatically detects all Queue, Exchange and Binding objects in Spring container, and creates corresponding resources on RabbitMQ server.

2. RabbitTemplate

Core utility class for sending and receiving messages. Provides multiple message sending methods (like convertAndSend, send) and receiving methods (like receive, receiveAndConvert).

3. SimpleMessageListenerContainer

Message listener container for async message consumption. Supports concurrent consumption, message acknowledgment and other features.


POM Configuration

<?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>icu.wzk</groupId>
    <artifactId>rabbitmq-demo-spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.amqp</groupId>
            <artifactId>spring-rabbit</artifactId>
            <version>2.2.7.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.2.6.RELEASE</version>
        </dependency>
    </dependencies>
</project>

XML Configuration (rabbit-context.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="
         http://www.springframework.org/schema/beans
         http://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/rabbit
         http://www.springframework.org/schema/rabbit/spring-rabbit.xsd">

    <rabbit:connection-factory id="connectionFactory"
                               host="localhost"
                               port="5672"
                               virtual-host="/"
                               username="admin"
                               password="secret" />

    <!-- RabbitTemplate: for sending/receiving messages -->
    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>

    <!-- RabbitAdmin: auto-declare Queue / Exchange / Binding -->
    <rabbit:admin id="rabbitAdmin" connection-factory="connectionFactory"/>

    <!-- Queue -->
    <rabbit:queue name="myqueue"/>

    <!-- Direct exchange + binding -->
    <rabbit:direct-exchange name="direct.biz.ex"
                            auto-declare="true"
                            auto-delete="false"
                            durable="false">
        <rabbit:bindings>
            <rabbit:binding queue="myqueue" key="dir.ex"/>
        </rabbit:bindings>
    </rabbit:direct-exchange>

</beans>

Java Code (StartApp.java)

package icu.wzk;

import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * Minimal loop example: Start Spring container -> Send message -> Pull one message from queue -> Close container
 *
 * Dependency prerequisites (consistent with rabbit-context.xml):
 * - Queue exists: myqueue
 * - Binding exists: myqueue <- (direct.biz.ex, routingKey=dir.ex)
 *
 * Note:
 * 1) convertAndSend(String routingKey, Object message) this overload uses "default exchange" (amq.default).
 *    Default exchange routing rules: routingKey must equal queue name, otherwise delivery fails.
 * 2) You now convertAndSend("dir.ex", ...) treats "dir.ex" as queue name for routing;
 *    If your queue is not called dir.ex but myqueue, then should explicitly specify exchange + routingKey:
 *    convertAndSend("direct.biz.ex", "dir.ex", ...)
 * 3) receiveAndConvert("myqueue") is synchronous pull (basicGet), empty queue returns null.
 */
public class StartApp {

    // Consistent with XML definition: exchange / routing key / queue
    private static final String EXCHANGE = "direct.biz.ex";
    private static final String ROUTING_KEY = "dir.ex";
    private static final String QUEUE = "myqueue";

    public static void main(String[] args) {
        // 1) Load Spring XML, initialize connection factory, RabbitTemplate, RabbitAdmin, queue/exchange/binding
        ClassPathXmlApplicationContext context =
                new ClassPathXmlApplicationContext("rabbit-context.xml");

        // 2) Get send/receive entry (underlying is usually RabbitTemplate)
        AmqpTemplate amqp = context.getBean(AmqpTemplate.class);

        // 3) Batch send 1000 messages to specified exchange + routingKey
        //    This way doesn't rely on default exchange's "routingKey==queue name" rule, routing clearer
        for (int i = 0; i < 1000; i++) {
            amqp.convertAndSend(EXCHANGE, ROUTING_KEY, "wzk" + i);
        }

        // 4) Synchronously pull one message from queue (may be null: queue empty / not yet delivered / taken by other consumer)
        Object msg = amqp.receiveAndConvert(QUEUE);
        System.out.println(msg);

        // 5) Close container: release connection and other resources
        context.close();
    }
}

Error Quick Reference

SymptomRoot CauseFix
Connection refused / connection timeout on startupRabbitMQ not started, port/host unreachable, firewall blockedtelnet host 5672 / RabbitMQ logs / container port mapping
ACCESS_REFUSED - Login was refused on startupWrong username/password or no vhost permissionRabbitMQ management console Users / vhost permissions
NOT_FOUND - no exchange ‘direct.biz.ex’ on startupExchange not declared successfully or declaration order/permission issueEnsure <rabbit:admin …/> exists and connection normal
Send no exception but queue has no messageUsed convertAndSend(routingKey, msg) via default exchange, routingKey≠queue name causing routing failureUse convertAndSend(exchange, routingKey, msg)
receiveAndConvert(“myqueue”) returns nullQueue empty/not yet delivered/taken by other consumerConfirm routing hit
PRECONDITION_FAILED - inequivalent arg ‘durable’ on startupSame name exchange/queue exists but with different attributesUnify durable/auto-delete configuration
Spring XML parsing reports schema/DTD related errorCannot load spring-rabbit.xsdEnsure schema accessible
Direct exchange bound but still not routingroutingKey not exact match; binding key wrongdirect requires exact match