使用Spring-amqp框架时,需要对Rabbitmq的基本概念有一定了解,可以先在RabbitMQ官网看完6步教程,并理解里面的代码及运行后,在来看Spring-amqp的使用会更好。下面的
Spring AMQP将Spring的核心概念用于基于AMQP的消息解决方案的开发中。这个框架提供了一个模版用于发送与接收消息,对原始Api进行了封装,简化了发送与接收的复杂性。该框架还通过 监听器容器提供了对消息驱动的POJO的支持。
http://projects.spring.io/spring-amqp/ 项目主页对Spring-amqp的介绍
该项目由两部分组成Spring-amqp是抽象层,而Spring-rabbit是RabbitMQ的实现。
通过监听器容器对要消费的消息进行异步处理。
通过RabbitTemplate模版进行发送与接收消息。
通过RabbitAdmin用来自动声明queues,exchanges,与bindings。
queues,exchanges,bindings是RabbitMq的三个核心概念。官方网站有详细介绍。
http://www.rabbitmq.com/tutorials/tutorial-four-java.html
下面的配置来自于项目首页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");
}
}
通常遇到问题后会查看日志文件,rabbitmq日志的默认位置在/var/log/rabbitmq目录下。
=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" />
=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 “.” “.” “.*”
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"/>
因此在最初的配置添加完这个配置后,问题解决。
上述配置是在一个项目中,消费者和生产者在一个项目。若消费者和生产者部署在不同机器如何配置?
通过学习RabbitMq 6步教程可以看到,消费者需要与RabbitMq建立连接,
并消费某消息队列上的消息。因此消费端同样配置好连接工厂,Template,队列,RabbitAdmin,及listener-container。然后通过Spring加载配置文件,创建SpringContext后,即可消费某队列上的消息。
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;