Spring-amqp 1.6.1 配置生产者与消费者及遇到的问题与解决

来源:互联网 发布:淘宝收入排行榜2015 编辑:程序博客网 时间:2024/06/06 03:35

使用Spring-amqp框架时,需要对Rabbitmq的基本概念有一定了解,可以先在RabbitMQ官网看完6步教程,并理解里面的代码及运行后,在来看Spring-amqp的使用会更好。下面的

1 Spring-amqp是什么?

Spring AMQP将Spring的核心概念用于基于AMQP的消息解决方案的开发中。这个框架提供了一个模版用于发送与接收消息,对原始Api进行了封装,简化了发送与接收的复杂性。该框架还通过 监听器容器提供了对消息驱动的POJO的支持。

http://projects.spring.io/spring-amqp/ 项目主页对Spring-amqp的介绍

该项目由两部分组成Spring-amqp是抽象层,而Spring-rabbit是RabbitMQ的实现。

2 Spring-amqp的特性

通过监听器容器对要消费的消息进行异步处理。
通过RabbitTemplate模版进行发送与接收消息。
通过RabbitAdmin用来自动声明queues,exchanges,与bindings。

queues,exchanges,bindings是RabbitMq的三个核心概念。官方网站有详细介绍。

http://www.rabbitmq.com/tutorials/tutorial-four-java.html

3 配置消息生产者端与消费者端进行消息发送与接收

下面的配置来自于项目首页http://projects.spring.io/spring-amqp/的快速入门demo,并进行了修改。

    <context:component-scan base-package="springamqp" />    <rabbit:connection-factory id="connectionFactory" username="sun" password="123456" host="192.168.2.133" port="5672" />    <rabbit:admin connection-factory="connectionFactory"/>    <rabbit:queue name="myQueue" id="myQueue" durable="true" auto-delete="false" exclusive="false"/>    <rabbit:direct-exchange name="myExchange">        <rabbit:bindings>            <rabbit:binding queue="myQueue" key="sun" />        </rabbit:bindings>    </rabbit:direct-exchange>    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"                      exchange="myExchange"                     routing-key="sun"     />    <rabbit:listener-container connection-factory="connectionFactory">        <rabbit:listener ref="consumer" queue-names="myQueue" />    </rabbit:listener-container>    <bean id="consumer" class="springamqp.listener.Consumer" />

这段配置会创建一个连接工厂,用来创建与rabbitmq的连接,若配置了rabbitmq的用户与密码需要设置username与password属性,最后才能成功建立连接。默认创建的是org.springframework.amqp.rabbit.connection.CachingConnectionFactory。

<rabbit:connection-factory id="connectionFactory" username="sun" password="123456" host="192.168.2.133" port="5672" />

配置RabbitAdmin,用来自动声明queues,exchanges,与bindings,这就避免了人工创建queues的操作。关于声明 queues,在Rabbitmq官网6步教程中有详细说明。

<rabbit:admin connection-factory="connectionFactory"/>

接下来是三个重要的配置queues,excahnges,与bindings。下面创建的exchange类型为direct-exchange,该exchange会根据routingKey与binding进行比较,最终将消息发送到绑定的队列。

    <rabbit:queue name="myQueue" id="myQueue" durable="true" auto-delete="false" exclusive="false"/>    <rabbit:direct-exchange name="myExchange">        <rabbit:bindings>            <rabbit:binding queue="myQueue" key="sun" />        </rabbit:bindings>    </rabbit:direct-exchange>

配置Template,通过template最后完成消息发送与接收,发送是要指定routing-key,及exchange 最后消息发向指定的队列。

    <rabbit:template id="amqpTemplate" connection-factory="connectionFactory"                      exchange="myExchange"                     routing-key="sun"     />

最后配置ListenerContainer最后完成消息的接收,这里接收者是一个POJO,因此需要通过配置method=”listen”来指定哪个方法来处理接收到的消息,如果配置的Bean实现了org.springframework.amqp.core.MessageListener接口,则无须指定method属性,默认会通过onMessage方法来处理接收到的消息。

    <rabbit:listener-container connection-factory="connectionFactory">        <rabbit:listener ref="consumer" queue-names="myQueue" method="listen" />    </rabbit:listener-container>    <bean id="consumer" class="springamqp.listener.Consumer" />

使用很简单,加载配置文件,然后发送消息并接收处理。

public class Producer {    public static void main(String[] args) throws InterruptedException {        ClassPathXmlApplicationContext ctx =                 new ClassPathXmlApplicationContext("classpath:rabbit-context.xml");        RabbitTemplate template = (RabbitTemplate) ctx.getBean("amqpTemplate");        template.convertAndSend("发送的消息");    }}public class Consumer {     public void listen(String foo) {              System.out.println("enter listen method");       }  }

4 完成上述任务时候遇到的问题与解决方法

通常遇到问题后会查看日志文件,rabbitmq日志的默认位置在/var/log/rabbitmq目录下。

4.1 问题1 访问密码错误

=INFO REPORT==== 25-Oct-2016::22:35:26 ===
accepting AMQP connection <0.7926.0> (192.168.2.99:62969 -> 192.168.2.59:5672)
ndshake_error,starting,0,
{amqp_error,access_refused,
“PLAIN login refused: user ‘sun’ - invalid credentials”,
‘connection.start_ok’}}

由于我在安装完rabbitMq后并创建了用户,并且连接时候使用了这个用户,但密码错误,而且我也忘记了当时创建账户的密码,导致产生这个问题。
随后通过命令修改密码rabbitmqctl change_password 用户 密码
并在下面的配置中重新设置password后,问题1解决。

<rabbit:connection-factory id="connectionFactory" username="sun" password="123456" host="192.168.2.133" port="5672" />

4.2 问题2 没有权限访问vhost

=ERROR REPORT==== 25-Oct-2016::22:34:17 ===
closing AMQP connection <0.7862.0> (192.168.2.99:62932 -> 192.168.2.59:5672):
{handshake_error,opening,0,
{amqp_error,access_refused,
“access to vhost ‘/’ refused for user ‘sun’”,
‘connection.open’}}

这个问题意思是说用户sun没有vhost /的访问权限。 vhost的概念可以在RabbitMQ in Action 2.4 Multiple tenants: virtual hosts and separation节看到。

因此通过rabbitmqctl赋予用户权限即可
rabbitmqctl set_permissions -p / sun “.” “.” “.*”

4.3 问题3 vhost下不存在队列

ERROR REPORT==== 25-Oct-2016::22:53:18 ===
connection <0.8847.0>, channel 1 - soft error:
{amqp_error,not_found,”no queue ‘myQueue’ in vhost ‘/’”,’queue.declare’}

字面意思看是说没有在vhost下找到队列。看过Rabbitmq官网6步教程的朋友都知道,queueDeclare来声明队列,不存在则创建,否则不操作。

而我最初的配置文件中是没有下面这行配置,而RabbitAdmin则是负责
声明queues,exchanges,与bindings。

<rabbit:admin connection-factory="connectionFactory"/>

因此在最初的配置添加完这个配置后,问题解决。

4.4 问题4 将生产者与消费者分离

上述配置是在一个项目中,消费者和生产者在一个项目。若消费者和生产者部署在不同机器如何配置?

通过学习RabbitMq 6步教程可以看到,消费者需要与RabbitMq建立连接,
并消费某消息队列上的消息。因此消费端同样配置好连接工厂,Template,队列,RabbitAdmin,及listener-container。然后通过Spring加载配置文件,创建SpringContext后,即可消费某队列上的消息。

4.5 官网Quick Start中消息监听器适配器的理解与使用

Quick Start中的一段代码如下,消费者通过handleMessage来消费消息。

Object listener = new Object() {        public void handleMessage(String foo) {            System.out.println(foo);        }    };    MessageListenerAdapter adapter = new MessageListenerAdapter(listener);    container.setMessageListener(adapter);

在接收消息时消费端一般都会继承org.springframework.amqp.core.MessageListener这个接口,当收到消息后void onMessage(Message message);方法会被调用。但有时希望通过自己指定一个方法来处理接收到的消息,这时就会用到org.springframework.amqp.rabbit.listener.adapter.MessageListenerAdapter类。该类通过适配器模式,最终使用户可以指定消息处理的方法,其源码实现如下。

    public void onMessage(Message message, Channel channel) throws Exception {        Object delegate = getDelegate();        //........省略        Object convertedMessage = extractMessage(message);        String methodName = getListenerMethodName(message, convertedMessage);        if (methodName == null) {            //抛出异常......        }        Object[] listenerArguments = buildListenerArguments(convertedMessage);        Object result = invokeListenerMethod(methodName, listenerArguments, message);        if (result != null) {            handleResult(result, message, channel);        }        else {            // 日志        }    }

上面的代码截取了调用用户委派对象的主要代码。可以看到主要是获取用户委派的对象,解析参数,方法,最终调用用户指定的对象完成消息的处理。

而下面这段配置ref的作用就是用来指定要适配的目标。并且最终是通过MessageListenerAdapter来处理,具体可以在该类的onMessage方法中设置断点,然后启动项目观察。

<rabbit:listener-container connection-factory="connectionFactory">    <rabbit:listener ref="foo" method="listen" queue-names="myQueue" /></rabbit:listener-container>

而Quickstart中之所以用handleMessage来处理消息是因为
使用者并未实现MessageListener接口,且为指定默认方法名,所以在解析监听方法名时,会返回默认的消息处理方法名,handleMessage。

    public static final String ORIGINAL_DEFAULT_LISTENER_METHOD = "handleMessage";    private String defaultListenerMethod = ORIGINAL_DEFAULT_LISTENER_METHOD;
0 0