本文简要介绍RabbitMQ提供的Java客户端中最基本的功能性接口/类及相关源码。

Mavan依赖:

<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>5.13.1</version>
</dependency>

0 AMQP

com.rabbitmq.client.AMQP接口将AMQP(Advanced Message Queue Protocol,高级消息队列协议)中的方法和消息属性封装成Java对象,便于以面向对象的思维模式进行编程。

该接口类图简要如下:

AMQP接口中包含许多内部类,大体可以分成三类:

0.1 协议信息

PROTOCOL内部类,保存了AMQP的协议版本等信息。

public static class PROTOCOL {
public static final int MAJOR = 0;
public static final int MINOR = 9;
public static final int REVISION = 1;
public static final int PORT = 5672;
}

0.2 方法

包括ConnectionChannelAccessExchangeQueueBasicTxConfirm内部类,分别封装了向Broker发送的不同方法的基本数据格式和内容。

它们都实现了com.rabbitmq.client.impl.Method抽象类(后续会介绍),在发送请求时,通过Method抽象类的toFrame()方法可以转换成Frame(帧),然后com.rabbitmq.client.impl.AMQConnection将其以二进制数据的方式通过TCP协议发送给Broker

它们都提供了各自的Builder,便于实例化方法对象(建造者模式)。例如,最常用的Publish方法类图简要如下:

通过如下代码实例化出Publish对象:

AMQP.Basic.Publish publish = new AMQP.Basic.Publish.Builder().exchange("myExchange").routingKey("myRoutingKey").mandatory(true).build();

在发送给Broker前可以通过如下代码将Publish对象转成帧:

Method method = (Method) publish;
Frame frame = method.toFrame(1);

com.rabbitmq.client.impl.AMQConnection对象管理着与Broker的连接,它通过如下代码将方法发送给Broker

AMQConnection connection = channel.getConnection();
connection.writeFrame(frame);

0.3 消息属性

BasicProperties内部类,封装了消息的基本属性。

它也提供了Builder,我们在发送消息时可以使用BasicProperties实例携带消息头信息,类图如下:

通过如下代码实例化出BasicProperties对象,并发送消息:

Map<String, Object> headers = new HashMap<>();
headers.put("color", "blue");
AMQP.BasicProperties properties = new AMQP.BasicProperties.Builder().headers(headers).expiration("10000").build();
channel.basicPublish("myExchange", "myRoutingKey", true, properties, "hello".getBytes());

BasicProperties对象在发送前最终会被转换成com.rabbitmq.client.impl.AMQContentHeader对象,代表AMQ消息内容的头。

AMQContentHeadertoFrame()方法也可以将其转换成Frame(帧),然后com.rabbitmq.client.impl.AMQConnection将其以二进制数据的方式通过TCP协议发送给Broker

AMQConnection connection = channel.getConnection();
Frame headerFrame = contentHeader.toFrame(channelNumber, body.length);
connection.writeFrame(headerFrame);

1 ConnectionFactory

com.rabbitmq.client.ConnectionFactory类是用来创建与RabbitMQ服务器连接(com.rabbitmq.client.Connection)的工厂类。简要类图如下:

ConnectionFactory内部封装了许多属性,用来设置与ConnectionSocket相关的连接信息。

它还提供了一套默认配置:

public static final String DEFAULT_VHOST = "/";
public static final String DEFAULT_HOST = "localhost";
public static final int DEFAULT_AMQP_PORT = AMQP.PROTOCOL.PORT; // 5672
public static final int DEFAULT_AMQP_OVER_SSL_PORT = 5671;
public static final String DEFAULT_PASS = "guest"; // CredentialsProvider.username
public static final String DEFAULT_USER = "guest"; // CredentialsProvider.password
private boolean nio = false;
private boolean automaticRecovery = true;

ConnectionFactory的基本使用如下:

ConnectionFactory connectionFactory = new ConnectionFactory();
Connection connection = connectionFactory.newConnection();//返回RecoveryAwareAMQConnection或AMQConnection对象

底层会创建出java.net.Socketjava.nio.channels.SocketChannel,代表与RabbitMQ服务器的TCP连接:

Socket socket = SocketFactory.getDefault().createSocket();
SocketChannel channel = SocketChannel.open();

SocketSocketChannel会被封装到com.rabbitmq.client.impl.FrameHandler中:

// nio==false,使用Socket(默认值)
SocketFrameHandler frameHandler = new SocketFrameHandler(sock, this.shutdownExecutor);
// nio==true,使用SocketChannel
SocketChannelFrameHandlerState state = new SocketChannelFrameHandlerState(channel, nioLoopContext, nioParams, sslEngine);
SocketChannelFrameHandler frameHandler = new SocketChannelFrameHandler(state);

FrameHandler中提供了readFrame()writeFrame(),分别可以从Socket/SocketChannel中读取或写入数据。

FrameHandler又会被封装到com.rabbitmq.client.impl.recovery.RecoveryAwareAMQConnectioncom.rabbitmq.client.impl.AMQConnection中:

// automaticRecovery==true,默认值
FrameHandler frameHandler = factory.create(addr, connectionName());
RecoveryAwareAMQConnection conn = createConnection(params, frameHandler, metricsCollector);
// automaticRecovery==false
FrameHandler handler = fhFactory.create(addr, clientProvidedName);
AMQConnection conn = createConnection(params, handler, metricsCollector);

因此,我们可以使用返回的Connection对象与RabbitMQ服务器进行交互。

2 Connection

com.rabbitmq.client.Connection接口代表与RabbitMQ服务器的TCP连接,类图简要如下:

Connection主要提供了createChannel()openChannel()方法,用来创建Channel。后者提供了几乎所有与RabbitMQ进行交互的方法,是项目中使用频率最高的一个接口。

Connection的基本使用如下:

Channe channel = connection.createChannel();

Connection的实现类主要包括以下几种,分别代表不同类型的连接:

  • AMQConnection类:代表最基本的与RabbitMQ服务器的连接。内部持有FrameHandler等成员变量,用来与服务器交互。
  • RecoveryAwareAMQConnection接口:代表自动重连的连接,内部没有方法,类似与标志性接口。
  • AutorecoveringConnection类:自动重连Connection的实现类,在非正常断开情况下会自动重连,例如I/O异常。它持有RecoveryAwareAMQConnection对象作为代理,从而间接可以使用FrameHandler对象与服务器进行交互。重连时,内部组件也会按如下顺序自动重连:
    • Exchanges
    • Queues
    • Bindings (both queue and exchange-to-exchange)
    • Consumers
  • RecoveryAwareAMQConnection:它是对AMQConnection的修改,主要用作AutorecoveringConnection的成员变量。它与AMQConnection主要区别在于它内部使用com.rabbitmq.client.impl.recovery.RecoveryAwareChannelN作为Channel

在项目中使用的实现类主要为AMQConnectionAutorecoveringConnection(根据ConnectionFactoryautomaticRecovery成员变量进行选择)。

AMQConnection.createChannel()方法会使用ChannelManager创建出ChannelN类型的通道:

ChannelManager cm = _channelManager;
Channel channel = cm.createChannel(this);
// 底层:
new ChannelN(connection, channelNumber, workService, this.metricsCollector);

AutorecoveringConnection.createChannel()方法会使用RecoveryAwareAMQConnection创建出RecoveryAwareChannelN类型的通道,并使用AutorecoveringChannel包装:

RecoveryAwareChannelN ch = (RecoveryAwareChannelN) delegate.createChannel();
final AutorecoveringChannel channel = new AutorecoveringChannel(this, delegateChannel);

AMQConnection中有一个十分重要的方法writeFrame(),可以将数据发送给RabbitMQ服务器:

public void writeFrame(Frame f) throws IOException {
_frameHandler.writeFrame(f);
_heartbeatSender.signalActivity();
}
// SocketFrameHandler
public void writeFrame(Frame frame) throws IOException {
synchronized (_outputStream) {
frame.writeTo(_outputStream);
}
}
public void writeTo(DataOutputStream os) throws IOException {
os.writeByte(type);
os.writeShort(channel);
if (accumulator != null) {
os.writeInt(accumulator.size());
accumulator.writeTo(os);
} else {
os.writeInt(payload.length);
os.write(payload);
}
os.write(AMQP.FRAME_END);
}

3 Channel

com.rabbitmq.client.Channel中封装了与RabbitMQ服务器交互的API,简要类图如下:

Channel的基本使用方式如下:

// 声明交换机
channel.exchangeDeclare("myExchange", BuiltinExchangeType.DIRECT);
// 声明队列
channel.queueDeclare("myQueue", true, false, false, null);
// 声明绑定
channel.exchangeBind("myQueue", "myExchange", "myRoutingKey");
// 发送消息
channel.basicPublish("myExchange", "myRoutingKey", true, null, "hello".getBytes());
// 订阅消息
channel.basicConsume("myQueue", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});
// 拉取消息
channel.basicGet("myQueue", true);

Channel是实现类包括以下几种:

  • ChannelNAMQP协议功能API的主要实现类。
  • RecoveryAwareChannelN:重写了basicAck()basicReject()basicNack()方法,对ChannelN功能进行扩展,实时跟踪delivery tag,对最新的tag进行响应。
  • AutorecoveringChannel:在connection重连时会自动恢复的通道,内部通过持有RecoveryAwareChannelN代理对象来实现具体操作。

3.1 ChannelN

com.rabbitmq.client.impl.ChannelN是对AMQP协议功能性API的主要实现类,它除了实现Channel中定义的AMQP协议功能性API,还继承了AMQChannel抽象类,通过其_connection成员变量可以在底层调用到SocketSocketChannel向RabbitMQ服务器进行读写操作。

除此之外,为了实现AMQP协议的特定功能,如消息确认机制。ChannelN内部封装了如下成员变量:

  • _consumers:消息消费者,以consumerTag作为key,用于监听消息。
  • returnListeners:监听RabbitMQ服务器找不到对应交换机时的返回消息(basicPublish方法发送消息时设置mandatoryimmediate)。
  • confirmListeners:监听RabbitMQ服务器的确认消息(acknack)。
  • defaultConsumer:默认的消息消费者。
  • dispatcher:启动线程执行_consumers中的任务。

ChannelN中监听消息的核心源码如下:

public boolean processAsync(Command command) throws IOException {
Method method = command.getMethod();
if (method instanceof Channel.Close) {
asyncShutdown(command);
return true;
}
if (isOpen()) {
// 根据不同方法类型调用对应的处理方法
if (method instanceof Basic.Deliver) {
processDelivery(command, (Basic.Deliver) method);
return true;
} else if (method instanceof Basic.Return) {
callReturnListeners(command, (Basic.Return) method);
return true;
} else if (method instanceof Channel.Flow) {
Channel.Flow channelFlow = (Channel.Flow) method;
synchronized (_channelMutex) {
_blockContent = !channelFlow.getActive();
transmit(new Channel.FlowOk(!_blockContent));
_channelMutex.notifyAll();
}
return true;
} else if (method instanceof Basic.Ack) {
Basic.Ack ack = (Basic.Ack) method;
callConfirmListeners(command, ack);
handleAckNack(ack.getDeliveryTag(), ack.getMultiple(), false);
return true;
} else if (method instanceof Basic.Nack) {
Basic.Nack nack = (Basic.Nack) method;
callConfirmListeners(command, nack);
handleAckNack(nack.getDeliveryTag(), nack.getMultiple(), true);
return true;
} else if (method instanceof Basic.RecoverOk) {
for (Map.Entry<String, Consumer> entry : Utility.copy(_consumers).entrySet()) {
this.dispatcher.handleRecoverOk(entry.getValue(), entry.getKey());
}
return false;
} else if (method instanceof Basic.Cancel) {
Basic.Cancel m = (Basic.Cancel)method;
String consumerTag = m.getConsumerTag();
Consumer callback = _consumers.remove(consumerTag);
if (callback == null) {
callback = defaultConsumer;
}
if (callback != null) {
try {
this.dispatcher.handleCancel(callback, consumerTag);
} catch (WorkPoolFullException e) {
throw e;
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConsumerException(this,
ex,
callback,
consumerTag,
"handleCancel");
}
} else {
LOGGER.warn("Could not cancel consumer with unknown tag {}", consumerTag);
}
return true;
} else {
return false;
}
} else {
if (method instanceof Channel.CloseOk) {
return false;
} else {
return true;
}
}
}

可见,该方法类似于SpringMVC中的DispatcherServlet,它会根据监听到Command对象的方法类型进行分发处理。接下来介绍的各成员变量方法调用的入口都在这个方法中。

3.1.1 ConsumerDispatcher

com.rabbitmq.client.impl.ConsumerDispatcher的作用是从线程池中获取空闲线程处理消息。它的主要作用是开启线程,而实际处理消息的业务逻辑在具体Consumer代理对象中处理。

例如,在处理生产者发布的消息时,ConsumerDispatcher会进行如下处理:

public void handleDelivery(final Consumer delegate,
final String consumerTag,
final Envelope envelope,
final AMQP.BasicProperties properties,
final byte[] body) throws IOException {
executeUnlessShuttingDown(
new Runnable() {
@Override
public void run() {
try {
delegate.handleDelivery(consumerTag,
envelope,
properties,
body);
} catch (Throwable ex) {
connection.getExceptionHandler().handleConsumerException(
channel,
ex,
delegate,
consumerTag,
"handleDelivery");
}
}
});
}

3.1.2 Consumer

com.rabbitmq.client.Consumer接口中定义了不同消息的处理方法,实例对象则表示消息消费者。

com.rabbitmq.client.DefaultConsumer是默认实现类,它实现了接口中的所有方法(空方法)。我们可以采取匿名内部类的方式,实现具体某个需要的方法,而不是实现所有方法。

我们可以使用如下代码添加消费者:

channel.basicConsume("myQueue", new DefaultConsumer(channel) {
@Override
public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println(new String(body));
}
});

ChannelN中调用消费者处理消息方法(handleDelivery())的源码如下:

protected void processDelivery(Command command, Basic.Deliver method) {
Basic.Deliver m = method;
Consumer callback = _consumers.get(m.getConsumerTag());
if (callback == null) {
if (defaultConsumer == null) {
throw new IllegalStateException("Unsolicited delivery -" +
" see Channel.setDefaultConsumer to handle this" +
" case.");
}
else {
callback = defaultConsumer;
}
} Envelope envelope = new Envelope(m.getDeliveryTag(),
m.getRedelivered(),
m.getExchange(),
m.getRoutingKey());
try {
metricsCollector.consumedMessage(this, m.getDeliveryTag(), m.getConsumerTag());
this.dispatcher.handleDelivery(callback,
m.getConsumerTag(),
envelope,
(BasicProperties) command.getContentHeader(),
command.getContentBody());
} catch (WorkPoolFullException e) {
throw e;
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConsumerException(this,
ex,
callback,
m.getConsumerTag(),
"handleDelivery");
}
}

需要注意的是,在调用 this.dispatcher.handleDelivery()之前,会首先调用Consumer callback = _consumers.get(m.getConsumerTag())根据consumerTag获取对应的消费者。因此,消费者处理消息是一对一的。

消费者其他方法的调用也可以在ChannelN.processAsync()中找到。

3.1.3 ReturnListener

com.rabbitmq.client.ReturnListener接口中定义了监听返回消息的通用方法handleReturn(),主要用于消息发布者监听返回消息。

消息发布者通过basicPublish方法发送消息时设置mandatoryimmediate,但RabbitMQ服务器找不到对应交换机时会返回消息。消息发布者通过往Channel对象中添加ReturnListener实现类,即可监听到返回消息:

channel.addReturnListener(new ReturnListener() {
@Override
public void handleReturn(int replyCode, String replyText, String exchange, String routingKey, AMQP.BasicProperties properties, byte[] body) throws IOException {
System.out.println("return message: " + new String(body));
}
});

ChannelN中处理返回消息的源码如下:

private void callReturnListeners(Command command, Basic.Return basicReturn) {
try {
for (ReturnListener l : this.returnListeners) {
l.handleReturn(basicReturn.getReplyCode(),
basicReturn.getReplyText(),
basicReturn.getExchange(),
basicReturn.getRoutingKey(),
(BasicProperties) command.getContentHeader(),
command.getContentBody());
}
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleReturnListenerException(this, ex);
} finally {
metricsCollector.basicPublishUnrouted(this);
}
}

ReturnListener是针对ChannelN级别的。接收到返回消息后,所有添加到ChannelN对象的ReturnListener监听器都会被调用。

3.1.4 ConfirmListener

com.rabbitmq.client.ConfirmListener接口中定义的监听RabbitMQ服务器确认消息(acknack)的回调方法,主要用于消息发布者使用。

基本使用代码如下:

channel.addConfirmListener(new ConfirmListener() {
@Override
public void handleAck(long deliveryTag, boolean multiple) throws IOException {
// 业务处理
} @Override
public void handleNack(long deliveryTag, boolean multiple) throws IOException {
// 业务处理
}
});

ChannelN中处理返回消息的源码如下:

private void callConfirmListeners(@SuppressWarnings("unused") Command command, Basic.Ack ack) {
try {
for (ConfirmListener l : this.confirmListeners) {
l.handleAck(ack.getDeliveryTag(), ack.getMultiple());
}
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConfirmListenerException(this, ex);
} finally {
metricsCollector.basicPublishAck(this, ack.getDeliveryTag(), ack.getMultiple());
}
} private void callConfirmListeners(@SuppressWarnings("unused") Command command, Basic.Nack nack) {
try {
for (ConfirmListener l : this.confirmListeners) {
l.handleNack(nack.getDeliveryTag(), nack.getMultiple());
}
} catch (Throwable ex) {
getConnection().getExceptionHandler().handleConfirmListenerException(this, ex);
} finally {
metricsCollector.basicPublishNack(this, nack.getDeliveryTag(), nack.getMultiple());
}
}

ConfirmListener是针对ChannelN级别的。接收到确认消息后,所有添加到ChannelN对象的ConfirmListener监听器都会被调用。

3.1.5 basicPublish()

前几小节讲述的都是RabbitMQ客户端监听从服务器响应的消息,本小节简要分析客户端发送消息的流程。

发送消息的基本方式如下:

channel.basicPublish("myExchange", "myRoutingKey", null, "hello".getBytes());
  1. ChannelN中的basicPublish()方法中执行如下代码,核心步骤如下:

    1. 将形参转换成AMQCommand对象中的CommandAssembler成员变量:exchangeroutingKeyBasic.Publish方法对象(Method),propertiesAMQContentHeader对象,bodyList<byte[]>对象。
    2. 调用transmit(command)方法,发送命令。
public void basicPublish(String exchange, String routingKey,
boolean mandatory, boolean immediate,
BasicProperties props, byte[] body)
throws IOException
{
if (nextPublishSeqNo > 0) {
unconfirmedSet.add(getNextPublishSeqNo());
nextPublishSeqNo++;
}
if (props == null) {
props = MessageProperties.MINIMAL_BASIC;
}
AMQCommand command = new AMQCommand(
new Basic.Publish.Builder()
.exchange(exchange)
.routingKey(routingKey)
.mandatory(mandatory)
.immediate(immediate)
.build(), props, body);
try {
transmit(command);
} catch (IOException e) {
metricsCollector.basicPublishFailure(this, e);
throw e;
}
metricsCollector.basicPublish(this);
}
  1. ChannelN中执行transmit()quiescingTransmit()方法,最终会调用AMQCommand.transmit()方法:
public void transmit(AMQCommand c) throws IOException {
synchronized (_channelMutex) {
ensureIsOpen();
quiescingTransmit(c);
}
} public void quiescingTransmit(AMQCommand c) throws IOException {
synchronized (_channelMutex) {
if (c.getMethod().hasContent()) {
while (_blockContent) {
try {
_channelMutex.wait();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
ensureIsOpen();
}
}
this._trafficListener.write(c);
c.transmit(this);
}
}
  1. AMQCommand中执行transmit()方法,核心步骤如下:

    1. 获取AMQConnection对象。
    2. 分别将AMQContentHeaderMethodList<byte[]>对象转换成Frame对象。
    3. 通过AMQConnection对象发送数据。
public void transmit(AMQChannel channel) throws IOException {
int channelNumber = channel.getChannelNumber();
AMQConnection connection = channel.getConnection(); synchronized (assembler) {
Method m = this.assembler.getMethod();
if (m.hasContent()) {
byte[] body = this.assembler.getContentBody(); Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length); int frameMax = connection.getFrameMax();
boolean cappedFrameMax = frameMax > 0;
int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length;
if (cappedFrameMax && headerFrame.size() > frameMax) {
String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
throw new IllegalArgumentException(msg);
}
connection.writeFrame(m.toFrame(channelNumber));
connection.writeFrame(headerFrame);
for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
int remaining = body.length - offset;
int fragmentLength = (remaining < bodyPayloadMax) ? remaining
: bodyPayloadMax;
Frame frame = Frame.fromBodyFragment(channelNumber, body,
offset, fragmentLength);
connection.writeFrame(frame);
}
} else {
connection.writeFrame(m.toFrame(channelNumber));
}
} connection.flush();
}

3.1.6 basicGet()

除了添加Comsumer监听器,我们还可以主动调用basicGet()向RabbitMQ服务器“拉取”消息。

basicGet()方法本质上是向RabbitMQ服务器发送一个Basic.Get请求,然后等待响应。

basicGet()方法的基本使用如下:

// 自动确认模式
GetResponse message = channel.basicGet("myQueue", true);
System.out.println(new String(message.getBody()));
// 手动确认模式
GetResponse myQueue = channel.basicGet("myQueue", false);
System.out.println(new String(message.getBody()));
channel.basicAck(myQueue.getEnvelope().getDeliveryTag(), false);
  1. ChannelN中的basicGet()方法中执行如下代码,核心步骤如下:

    1. 将形参转换成AMQCommand对象中的CommandAssembler成员变量:queueBasic.Get方法对象(Method),propertiesAMQContentHeader对象,bodyList<byte[]>对象。
    2. 调用exnWrappingRpc(command)方法,发送命令。
    3. 等待响应replyCommand,并封装成GetResponse对象返回,
   public GetResponse basicGet(String queue, boolean autoAck) throws IOException{
validateQueueNameLength(queue);
AMQCommand replyCommand = exnWrappingRpc(new Basic.Get.Builder()
.queue(queue)
.noAck(autoAck)
.build());
Method method = replyCommand.getMethod();
if (method instanceof Basic.GetOk) {
Basic.GetOk getOk = (Basic.GetOk)method;
Envelope envelope = new Envelope(getOk.getDeliveryTag(),
getOk.getRedelivered(),
getOk.getExchange(),
getOk.getRoutingKey());
BasicProperties props = (BasicProperties)replyCommand.getContentHeader();
byte[] body = replyCommand.getContentBody();
int messageCount = getOk.getMessageCount();
metricsCollector.consumedMessage(this, getOk.getDeliveryTag(), autoAck);
return new GetResponse(envelope, props, body, messageCount);
} else if (method instanceof Basic.GetEmpty) {
return null;
} else {
throw new UnexpectedMethodError(method);
}
}
public AMQCommand exnWrappingRpc(Method m) throws IOException {
try {
return privateRpc(m);
} catch (AlreadyClosedException ace) {
throw ace;
} catch (ShutdownSignalException ex) {
throw wrap(ex);
}
}
  1. ChannelN中的privateRpc()方法中执行如下代码,核心步骤如下:

    1. 实例化SimpleBlockingRpcContinuation对象,用于获取响应。
    2. 调用rpc(m, k)方法,发送Basic.Get请求。
    3. 调用k.getReply()方法,等待响应并返回。
   private AMQCommand privateRpc(Method m) throws IOException, ShutdownSignalException{
SimpleBlockingRpcContinuation k = new SimpleBlockingRpcContinuation(m);
rpc(m, k); // 发送请求
if(_rpcTimeout == NO_RPC_TIMEOUT) {
return k.getReply(); // 等待响应
} else {
try {
return k.getReply(_rpcTimeout);
} catch (TimeoutException e) {
throw wrapTimeoutException(m, e);
}
}
} public void rpc(Method m, RpcContinuation k) throws IOException {
synchronized (_channelMutex) {
ensureIsOpen();
quiescingRpc(m, k);
}
} public void quiescingRpc(Method m, RpcContinuation k) throws IOException {
synchronized (_channelMutex) {
enqueueRpc(k);
quiescingTransmit(m);
}
} public void enqueueRpc(RpcContinuation k) {
doEnqueueRpc(() -> new RpcContinuationRpcWrapper(k));
} public void quiescingTransmit(Method m) throws IOException {
synchronized (_channelMutex) {
quiescingTransmit(new AMQCommand(m));
}
} public void quiescingTransmit(AMQCommand c) throws IOException {
synchronized (_channelMutex) {
if (c.getMethod().hasContent()) {
while (_blockContent) {
try {
_channelMutex.wait();
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
ensureIsOpen();
}
}
this._trafficListener.write(c);
c.transmit(this);
}
}
  1. 最终,在AMQCommand中执行transmit()方法,核心步骤如下:

    1. 获取AMQConnection对象。
    2. 分别将AMQContentHeaderMethodList<byte[]>对象转换成Frame对象。
    3. 通过AMQConnection对象发送数据。
   public void transmit(AMQChannel channel) throws IOException {
int channelNumber = channel.getChannelNumber();
AMQConnection connection = channel.getConnection();
synchronized (assembler) {
Method m = this.assembler.getMethod();
if (m.hasContent()) {
byte[] body = this.assembler.getContentBody();
Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length);
int frameMax = connection.getFrameMax();
boolean cappedFrameMax = frameMax > 0;
int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length; if (cappedFrameMax && headerFrame.size() > frameMax) {
String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
throw new IllegalArgumentException(msg);
}
connection.writeFrame(m.toFrame(channelNumber));
connection.writeFrame(headerFrame); for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
int remaining = body.length - offset; int fragmentLength = (remaining < bodyPayloadMax) ? remaining
: bodyPayloadMax;
Frame frame = Frame.fromBodyFragment(channelNumber, body,
offset, fragmentLength);
connection.writeFrame(frame);
}
} else {
connection.writeFrame(m.toFrame(channelNumber));
}
}
connection.flush();
}

4 AMQCommand

com.rabbitmq.client.impl.AMQCommand类实现了com.rabbitmq.client.Command接口,其成员变量CommandAssembler对象是AMQP规范中methodheaderbody的容器。

AMQCommand中提供了一个十分重要的方法:transmit(AMQChannel)。调用该方法能够将methodheaderbody通过Connection发送给RabbitMQ服务器。该方法在前几个小节都有介绍,核心步骤如下:

  1. 获取AMQConnection对象。
  2. 分别将AMQContentHeaderMethodList<byte[]>对象转换成Frame对象。
  3. 通过AMQConnection对象发送数据。
public void transmit(AMQChannel channel) throws IOException {
int channelNumber = channel.getChannelNumber();
AMQConnection connection = channel.getConnection(); synchronized (assembler) {
Method m = this.assembler.getMethod();
if (m.hasContent()) {
byte[] body = this.assembler.getContentBody();
Frame headerFrame = this.assembler.getContentHeader().toFrame(channelNumber, body.length);
int frameMax = connection.getFrameMax();
boolean cappedFrameMax = frameMax > 0;
int bodyPayloadMax = cappedFrameMax ? frameMax - EMPTY_FRAME_SIZE : body.length;
if (cappedFrameMax && headerFrame.size() > frameMax) {
String msg = String.format("Content headers exceeded max frame size: %d > %d", headerFrame.size(), frameMax);
throw new IllegalArgumentException(msg);
}
connection.writeFrame(m.toFrame(channelNumber));
connection.writeFrame(headerFrame);
for (int offset = 0; offset < body.length; offset += bodyPayloadMax) {
int remaining = body.length - offset;
int fragmentLength = (remaining < bodyPayloadMax) ? remaining
: bodyPayloadMax;
Frame frame = Frame.fromBodyFragment(channelNumber, body,
offset, fragmentLength);
connection.writeFrame(frame);
}
} else {
connection.writeFrame(m.toFrame(channelNumber));
}
}
connection.flush();
}

4.1 CommandAssembler

com.rabbitmq.client.impl.CommandAssembler类中封装了AMQP规范中methodheaderbody

4.1.1 Method

com.rabbitmq.client.impl.Method抽象类代表AMQP规范中method,我们平常所使用的com.rabbitmq.client.AMQP接口中的ConnectionChannelAccessExchangeQueueBasicTxConfirm等内部类都实现了该抽象类。

我们调用channel.basicPublish()等方法向RabbitMQ服务器发送消息,或者从通过注册Consumer监听RabbitMQ服务器的消息时,都会将method数据段转换成Method实现类进行处理。

Method.toFrame()方法则能将自己转换成Frame对象,进行发送。

4.1.2 AMQContentHeader

com.rabbitmq.client.impl.AMQContentHeader抽象类代表AMQP规范中headerchannel.basicPublish()方法形参BasicProperties实现了该抽象类,我们可以通过该对象为消息设置属性。

我们调用channel.basicPublish()等方法向RabbitMQ服务器发送消息,或者从通过注册Consumer监听RabbitMQ服务器的消息时,都会将method数据段转换成AMQContentHeader实现类进行处理。

AMQContentHeader.toFrame()方法则能将自己转换成Frame对象,进行发送。

5 Frame

com.rabbitmq.client.impl.Frame代表AMQP wire-protocol frame(帧),主要包含以下成员变量:

  • type:帧类型。
  • channel:所属通道。
  • payload:输入载荷。
  • accumulator:输出载荷。

Frame还提供了静态方法readFrom(),可以从输入流中读取到Frame对象,主要提供给FrameHandler.readFrame()方法调用:

public static Frame readFrom(DataInputStream is) throws IOException {
int type;
int channel;
try {
type = is.readUnsignedByte();
} catch (SocketTimeoutException ste) {
return null; // failed
}
if (type == 'A') {
protocolVersionMismatch(is);
}
channel = is.readUnsignedShort();
int payloadSize = is.readInt();
byte[] payload = new byte[payloadSize];
is.readFully(payload);
int frameEndMarker = is.readUnsignedByte();
if (frameEndMarker != AMQP.FRAME_END) {
throw new MalformedFrameException("Bad frame end marker: " + frameEndMarker);
}
return new Frame(type, channel, payload);
}

[RabbitMQ]Java客户端:源码概览的更多相关文章

  1. 关于FastDFS Java客户端源码中的一个不太明白的地方

    下面代码是package org.csource.fastdfs下TrackerGroup.java文件中靠近结束的一段代码,我下载的这个源码的版本是1.24. /** * return connec ...

  2. Java集合源码学习(一)集合框架概览

    >>集合框架 Java集合框架包含了大部分Java开发中用到的数据结构,主要包括List列表.Set集合.Map映射.迭代器(Iterator.Enumeration).工具类(Array ...

  3. Java ArrayList源码剖析

    转自: Java ArrayList源码剖析 总体介绍 ArrayList实现了List接口,是顺序容器,即元素存放的数据与放进去的顺序相同,允许放入null元素,底层通过数组实现.除该类未实现同步外 ...

  4. Java——LinkedHashMap源码解析

    以下针对JDK 1.8版本中的LinkedHashMap进行分析. 对于HashMap的源码解析,可阅读Java--HashMap源码解析 概述   哈希表和链表基于Map接口的实现,其具有可预测的迭 ...

  5. swift实现饭否应用客户端源码

    swift 版 iOS 饭否客户端 源码下载:http://code.662p.com/view/13318.html 饭否是中国大陆地区第一家提供微博服务的网站,被称为中国版Twitter.用户可通 ...

  6. android版高仿淘宝客户端源码V2.3

    android版高仿淘宝客户端源码V2.3,这个版本我已经更新到2.3了,源码也上传到源码天堂那里了,大家可以看一下吧,该应用实现了我们常用的购物功能了,也就是在手机上进行网购的流程的,如查看产品(浏 ...

  7. Java集合源码分析(四)Vector<E>

    Vector<E>简介 Vector也是基于数组实现的,是一个动态数组,其容量能自动增长. Vector是JDK1.0引入了,它的很多实现方法都加入了同步语句,因此是线程安全的(其实也只是 ...

  8. Java集合源码分析(三)LinkedList

    LinkedList简介 LinkedList是基于双向循环链表(从源码中可以很容易看出)实现的,除了可以当做链表来操作外,它还可以当做栈.队列和双端队列来使用. LinkedList同样是非线程安全 ...

  9. Java集合源码分析(二)ArrayList

    ArrayList简介 ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存. ArrayList不是线程安全的,只能用在单线程环境下,多线 ...

  10. C#中国象棋+游戏大厅 服务器 + 客户端源码

    来源:www.ajerp.com/bbs C#中国象棋+游戏大厅 服务器 + 客户端源码 源码开源 C#版中国象棋(附游戏大厅) 基于前人大虾的修改版 主要用委托实现 服务器支持在线人数,大厅桌数的设 ...

随机推荐

  1. C#的生产者和消费者 实例

    class Program { //写线程将数据写入myData static int myData = 100; //读写次数 const int readWriteCount = 20; //fa ...

  2. 【C++】 四种强制类型转换(static_cast 与 dynamic_cast 的区别!)

    强制类型转换 1. static_cast 2. dynamic_cast 3. const_cast 4. reinterpret_cast 5. 为什么要需要四种类型转换? 1. static_c ...

  3. 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建一个版本

    我们可以使用官方 sentry-cli 工具操作 Sentry API,从而来为你的项目管理一些数据.它主要用于管理 iOS.Android 的调试信息文件,以及其他平台的版本(release)和源代 ...

  4. kubebuilder实战之六:构建部署运行

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...

  5. Ubuntu 16.04 + Win10双系统 启动Ubuntu进入命令行 无界面

    Ubuntu 16.04 + Win10双系统,启动Ubuntu时候报错,并入命令行(无界面). 原因:可能是双系统兼容性问题 解决办法: 重启系统,进入Win10 然后在Win10中重启电脑. 重启 ...

  6. MySQL的主从复制步骤详解及常见错误解决方法

    mysql主从复制(replication同步)现在企业用的比较多,也很成熟.它有以下优点: 1.降低主服务器压力,可在从库上执行查询工作. 2.在从库上进行备份,避免影响主服务器服务. 3.当主库出 ...

  7. JavaScript之创建对象的模式

    使用Object的构造函数可以创建对象或者使用对象字面量来创建单个对象,但是这些方法有一个明显的缺点:使用相同的一个接口创建很多对象,会产生大量的重复代码. (一)工厂模式 这种模式抽象了创建具体对象 ...

  8. Spring事务管理回滚问题

    Spring事务管理不能回滚问题 在前段时间学习SpringMVC的练习中,碰到声明式事务管理时,事务不能回滚的情况,通过查看博客和资料,解决了问题. 原因 导致Spring事务管理不能回滚的原因有两 ...

  9. 使用shell脚本实现everthing的功能

    我们知道,在 Windows 下,有一款非常实用的神器,叫作 Everything ,它可以在极短的时间里,搜索出来你所想要的文件/目录,如下图示: Linux 下也有一些类似于 everything ...

  10. 显示锁lock

    一.内置锁sync 和 显示锁lock概念 1.synv锁又叫内置锁,不能中断,拿不到无限等待即阻塞: java自带关键字: 隐式可重入: 重入锁:锁对应对象要多次调用对应方法,如递归 2. lock ...