Overview
RabbitMQ Java client使用com.rabbitmq.clients
作为顶层的包,关键的类和接口是:
- Channel
- Connection
- ConnectionFactory
- Consumer
rabbitmq是通过Channel
接口进行协议操作的。Connection
用于打开Channel
,注册连接的生命周期时间的处理程序和关闭不再需要的连接。Connection
是通过ConnectionFactory
进行实例化的,该接口可以配置各种连接的设置,如vhost或者username。
rabbitmq的jar包的下载地址为:
http://repo1.maven.org/maven2/com/rabbitmq/amqp-client/5.0.0/amqp-client-5.0.0.jar
Maven:
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.0.0</version>
</dependency>
Gradle:
dependencies {
compile 'com.rabbitmq:amqp-client:5.0.0'
}
Connections和Channels
Connection
和Channel
类是整个API的核心,代表AMQP中的connection
和channel
,在使用前必须import,如下:
import com.rabbit.clients.Connection;
import com.rabbit.clients.Channel;
Connecting to a broker(连接代理)
下面的代码是用来连接broker的:
ConnectionFactory factory = new ConnectionFactory();
factory.setUsername(userName);
factory.setPassword(password);
factory.setVirtualHost(virtualHost);
factory.setHost(hostName);
factory.setPort(portNumber);
Connection conn = factory.newConnection();
当rabbitmq服务器是在本地运行时,上面的那些参数是默认的。
另外,URls可以这样使用:
ConnectionFactory factory = new ConnectionFactory();
factory.setUri("amqp://userName:password@hostName:portNumber/virtualHost");
Connection conn = factory.newConnection();
同样,如果rabbitmq服务在本地运行,那么这些参数是默认的。
Connection
可以用来打开一个channel
,channel
可以用来发布可接收message
Channel channel = conn.creatChannel();
关闭channel
和connection
如下,关闭channel
是一个很好的行为,但是不是必须的,当connection
关闭时,channel
会自动关闭。
channel.close();
conn.close();
使用Exchanges和Queues
客户端应用工作时是与exchange和queue相关联的。在使用它们之前必须声明,继续上面的例子,下面的代码声明一个exchange和queue,然后将它们绑定在一起:
channel.exchangeDeclare(exchangeName,"direct",true);//声明一个exchange
String queueName = channel.queueDeclare().getQueue();//获取声明queue的名字
channel.queueBind(queueName,exchangeName,routingKey);//将exchange和queue绑定
上面的代码声明了exchange和queue,其中exchange是持久的,非自动删除的,type为“direct”,queue是非持久的,独有的,自动删除的,其名称是自动生成的(也可以自己指定)。queue会和具有相同routing key 的exchange绑定。
channel.exchangeDeclare(exchangeName, "direct", true);
channel.queueDeclare(queueName, true, false, false, null);
channel.queueBind(queueName, exchangeName, routingKey);
上面这段代码与之前的不同之处在于queue的声明,这段代码是根据客户端设置的queue声明的queue。其他的则相同。
exchangeDeclare和queueDeclare是重载的方法,具有不同形式的参数,你也可以重写这些方法以便你自己使用。
Publishing message(发布消息)
使用channel.basicPublish
发布消息:
bytes[] messageBodyBytes="Hello World!".getBytes();
channel.basicPublish(exchangeName,routingkey,null,messageBodyBytes);
为了更好的控制消息的发布,也可以使用更复杂的重载的方法,mandatory
和消息属性MessageProperties.PERSISTENT_TEXT_PLAIN
:
channel.basicPublish(exchangeName, routingKey, mandatory,
MessageProperties.PERSISTENT_TEXT_PLAIN,
messageBodyBytes);
你也可以使用Builder
自定义一些属性:如delivery mode 2,priority 1,content-type "text.plain"
:
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.contentType("text/plain")
.deliveryMode(2)
.priority(1)
.userId("bob")
.build()),
messageBodyBytes);
下面的例子说明了如何发布自定义headers的消息:
Map<String, Object> headers = new HashMap<String, Object>();
headers.put("latitude", 51.5252949);
headers.put("longitude", -0.0905493);
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.headers(headers)
.build()),
messageBodyBytes);
发布一个带期限(expiration)的message:
channel.basicPublish(exchangeName, routingKey,
new AMQP.BasicProperties.Builder()
.expiration("60000")
.build()),
messageBodyBytes);
BasicProperties
是AMQP(自动生成的容器类)的内部类。
Channels 和 并发注意事项(线程安全)
一般来说,不能在线程之间共享Channel实例,应用应该一个线程声明一个Channel实例而不是在多个线程之间共享一个Channe实例,否则会出现错误。
在一个consume和一个publish的两个线程之间共享channel是安全的。
服务器推送的交付是同时进行的,同时也保证了每一个channel的订阅都被保留。调度机制使用了java.util.concurrent.ExecutorService
(一个连接一个服务)。由一个ConnectionFactory
创建的Connection
由一个处理程序处理是可以的。
当使用手动确认机制时,就必须要考虑确认消息是属于哪个线程的。这与接收处理分发的消息的线程(如:Consumer#handleDelivery下发消息到不同的线程)是不同的。将多个确认消息参数都设置为true
是不安全的行为,这会导致双重确认,channel级的协议异常和channel关闭。一次确认一个消息是安全的!
接收订阅的消息
import com.rabbitmq.client.Consumer;
import com.rabbitmq.client.DefaultConsumer;
最有效的接收消息的方式是订阅(subscription),然后订阅的消息就自动的被分发,而不是去请求消息。
在调用与Consumer相关的方法时,个人订阅总是与consumer tag相关。consumer tag可以是由客户端或者服务器端生成的一个标识符。consumer tag与取消消费者有关。不同的Consumer实例必须有不同的consumer tag,不要在连接上重复使用consumer tag。
实现consumer的最简单的方法是将类DefaultConsumer子类化。一个子类的对象可以在basicConsume调用中传递,用于建立订阅。
boolean autoAck = false;
channel.basicConsume(queueName, autoAck, "myConsumerTag",
new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag,
Envelope envelope,
AMQP.BasicProperties properties,
byte[] body)
throws IOException
{
String routingKey = envelope.getRoutingKey();
String contentType = properties.getContentType();
long deliveryTag = envelope.getDeliveryTag();
// (process the message components here ...)
channel.basicAck(deliveryTag, false);
}
});
Consumers可以使用handleCancelOk和handleCancel方法分别表示明确和不明确的取消操作。
可以通过channel.basicCancel取消一个Consumer:
channel.basicCancel(consumerTag);
最好一个consumer一个channel!
取消息(”Pull API”)
可以使用channel.basicGet方法取message,返回的是一个GetResponse对象,从该对象中可以提取header信息(properties)和消息体。
boolean autoAck = false;
GetResponse response = channel.basicGet(queueName, autoAck);
if (response == null) {
// No message retrieved.
} else {
AMQP.BasicProperties props = response.getProps();
byte[] body = response.getBody();
long deliveryTag = response.getEnvelope().getDeliveryTag();
...