- AMQP抽象
Spring AMQP包含了很多模块,每一个模块在发布的时候都已一个Jar包的方式提供。这些模块包括:spring-amqp,spring-rabbit,spring-erlang。spring-amqp包含
org.springframework.amqp.core包。在这个包里面你将发现代表着AMQP核心模型的类。我们的意图是提供高度的抽象,这个抽象不会依赖任何一个具体的AMQP代理实现或者客户端实现。每一个用户的代码将游离于不同的实现,因为它是针对抽象层开发的。这些抽象最终会被一些具体的消息代理实现为具体的模块,例如spring-rabbit。由于AMQP的操作是在协议层,所以理论上说,RabbitMQ可以被其他支持同样协议版本的代理使用,但是到目前为止没有进行测试。
- 消息
AMQP 0-8 0-9-1的规定没有定义Message类和接口。然而,当我们执行诸如‘
basicPublish’的操作,内容将作为字节数组进行传递,附加属性将使用额外的参数进行传递。Spring AMQP定义了消息类,作为更为广泛的AMQP领域模型中的一部分。消息类的目的是简单封装了内容与属性在一个实体中,从而API变得更简单。消息类的定义很直观。
public class Message implements Serializable {
private static final long serialVersionUID = -7177590352110605597L;
private static final String ENCODING = Charset.defaultCharset().name();
private final MessageProperties messageProperties;
private final byte[] body;
public Message(byte[] body, MessageProperties messageProperties) {
this.body = body;
this.messageProperties = messageProperties;
}
public byte[] getBody() {
return this.body;
}
public MessageProperties getMessageProperties() {
return this.messageProperties;
}
@Override
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("(");
buffer.append("Body:'" + this.getBodyContentAsString() + "'");
if (messageProperties != null) {
buffer.append(messageProperties.toString());
}
buffer.append(")");
return buffer.toString();
}
private String getBodyContentAsString() {
if (body == null) {
return null;
}
try {
String contentType = (messageProperties != null) ? messageProperties.getContentType() : null;
if (MessageProperties.CONTENT_TYPE_SERIALIZED_OBJECT.equals(contentType)) {
return SerializationUtils.deserialize(body).toString();
}
if (MessageProperties.CONTENT_TYPE_TEXT_PLAIN.equals(contentType)
|| MessageProperties.CONTENT_TYPE_JSON.equals(contentType)
|| MessageProperties.CONTENT_TYPE_JSON_ALT.equals(contentType)
|| MessageProperties.CONTENT_TYPE_XML.equals(contentType)) {
return new String(body, ENCODING);
}
}
catch (Exception e) {
// ignore
}
// Comes out as '[B@....b' (so harmless)
return body.toString()+"(byte["+body.length+"])";
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(body);
result = prime * result + ((messageProperties == null) ? 0 : messageProperties.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Message other = (Message) obj;
if (!Arrays.equals(body, other.body)) {
return false;
}
if (messageProperties == null) {
if (other.messageProperties != null) {
return false;
}
}
else if (!messageProperties.equals(other.messageProperties)) {
return false;
}
return true;
}
MessageProperties
类中定义了一些公共的属性,例如‘
messageId
’,‘
timestamp
’,‘
contentType
’,这些属性同样可以通过调用方法
setHeader(String key, Object value)
进行扩展。
- 交换
Exchange
接口代表一个AMQP Exchange,它是消息生产者消息所发送到的地方,一个消息代理中的虚拟主中的每一个Exchange的名称必须唯一,它还有其他一些属性。
public interface Exchange {
String getName();
String getExchangeType();
boolean isDurable();
boolean isAutoDelete();
Map<String, Object> getArguments();
}
可以看到,Exchange有一个类型'type',这个类型在ExchangeTypes中定义。基本的类型包括:Direct,Topic,Fanout,和Headers。在核心包中你会发现Exchange接口针对每一种类型的实现。这些不同类型的Exchange因为它们处理队列绑定的方式不一样而不同。例如,Direct Exchange允许通过固定的路由键来和队列进行绑定。Topic Exchange支持通过'*' '#'通配符分别代表一个或者零个或者多个进行绑定。Fanout Exchange将不考虑路由键,将消息发送到绑定的所有队列中。
AMQP规范要求任何一个消息代理必须提供一个’default‘ Direct Exchange,这个Exchange没有名称。所有声明的队列都和这个Exchange绑定,使用它们的名称作为路由键。
- 队列
队列类存储着消息的组件,消息消费者从队列中接收到消息。
public class Queue {
private final String name;
private volatile boolean durable;
private volatile boolean exclusive;
private volatile boolean autoDelete;
private volatile Map<String, Object> arguments;
/**
* The queue is durable, non-exclusive and non auto-delete.
*
* @param name the name of the queue.
*/
public Queue(String name) {
this(name, true, false, false);
}
// Getters and Setters omitted for brevity
注意构造函数接收队列的名称。取决于具体的实现,admin模板提供了方法来产生唯一名称的队列。像这类的队列可以作为’reply-to'地址或者一些临时的场景。由于这些原因,自动产生的队列的‘exclusive’和‘autoDelete’属性通常被设置为true。
- 绑定
我们知道生产者发送消息给到一个Exchange,消费者从队列中取到消息,队列和Exchange之间的绑定至关重要,它们通过消息来连接着生产者和消费者。在Spring AMQP中我们使用Binding这个类来代表这层连接关系。下面我们来看看Queue和Exchange之间的绑定:
Queue和DirectExchange之间通过国定的路由键进行绑定:
new Binding(someQueue, someDirectExchange, "foo.bar")
Queue和TopicExchange之间通过通配路由键进行绑定:
new Binding(someQueue, someTopicExchange, "foo.*")
Queue和FanoutExchange之间的绑定不需要路由键:
new Binding(someQueue, someFanoutExchange)
我们同样提供了
BindingBuilder
使用流式API风格来简化绑定:
Binding b = BindingBuilder.bind(someQueue).to(someTopicExchange).with("foo.*");
Binding实例自身仅仅持有关于连接的数据。换句话说,它不是一个活跃的组件。然而你在后面将会看到,Binding实例被AmqpAdmin类用来出发消息代理的行为。你同样可以看到Binding实例可以通过注解在@Configuration的类中使用@Bean进行定义。
还存在一些基础的类,这些类进一步简化了AMQP
相关的Queue,Exchange,Binding的创建。AmqpTemplate这个类同样在核心包中定义,它将在后面章节中讨论。