AMQP-628: Master to 2.0, SF 5.0

JIRA: https://jira.spring.io/browse/AMQP-628

Remove log4j Appender (deprecated in 1.6.2).

Rename/Remove Schemas

Simple polishing according PR comments
This commit is contained in:
Gary Russell
2016-07-28 12:10:47 -04:00
committed by Artem Bilan
parent 208775dd2e
commit 2ee8c34a8c
21 changed files with 237 additions and 7861 deletions

1
.gitignore vendored
View File

@@ -9,6 +9,7 @@
.gradle
.project
.settings
.checkstyle
bin
build
.DS_Store

View File

@@ -78,9 +78,8 @@ subprojects { subproject ->
}
}
// ensure JDK 6 compatibility
sourceCompatibility=1.6
targetCompatibility=1.6
sourceCompatibility=1.8
targetCompatibility=1.8
ext {
cglibVersion = '3.1'
@@ -97,7 +96,7 @@ subprojects { subproject ->
rabbitmqVersion = project.hasProperty('rabbitmqVersion') ? project.rabbitmqVersion : '3.6.3'
rabbitmqHttpClientVersion = '1.0.0.RELEASE'
springVersion = project.hasProperty('springVersion') ? project.springVersion : '4.2.7.RELEASE'
springVersion = project.hasProperty('springVersion') ? project.springVersion : '5.0.0.M1'
springRetryVersion = '1.1.3.RELEASE'
}

View File

@@ -1,2 +1,2 @@
version=1.6.2.BUILD-SNAPSHOT
version=2.0.0.BUILD-SNAPSHOT
org.gradle.daemon=true

View File

@@ -1,674 +0,0 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.log4j;
import java.io.UnsupportedEncodingException;
import java.util.Calendar;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.log4j.AppenderSkeleton;
import org.apache.log4j.Layout;
import org.apache.log4j.Level;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.spi.ErrorCode;
import org.apache.log4j.spi.LocationInfo;
import org.apache.log4j.spi.LoggingEvent;
import org.apache.log4j.spi.ThrowableInformation;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.HeadersExchange;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.AbstractConnectionFactory;
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.core.DeclareExchangeConnectionListener;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.LogAppenderUtils;
/**
* A Log4J appender that publishes logging events to an AMQP Exchange.
* <p>
* A fully-configured AmqpAppender, with every option set to their defaults, would look like this:
* <pre class="code">
* log4j.appender.amqp=org.springframework.amqp.rabbit.log4j.AmqpAppender
* #-------------------------------
* ## Connection settings
* #-------------------------------
* log4j.appender.amqp.host=localhost
* log4j.appender.amqp.port=5672
* #log4j.appender.amqp.addresses=foo:5672,bar:5673 - overrides host and port if present
* log4j.appender.amqp.username=guest
* log4j.appender.amqp.password=guest
* log4j.appender.amqp.virtualHost=/
* #-------------------------------
* ## Exchange name and type
* #-------------------------------
* log4j.appender.amqp.exchangeName=logs
* log4j.appender.amqp.exchangeType=topic
* #-------------------------------
* ## Log4J-format pattern to use to create a routing key.
* ## The application id is available as %X{applicationId}.
* #-------------------------------
* log4j.appender.amqp.routingKeyPattern=%c.%p
* #-------------------------------
* ## Whether or not to declare this configured exchange
* #-------------------------------
* log4j.appender.amqp.declareExchange=false
* #-------------------------------
* ## Flags to use when declaring the exchange
* #-------------------------------
* log4j.appender.amqp.durable=true
* log4j.appender.amqp.autoDelete=false
* #-------------------------------
* ## Message properties
* #-------------------------------
* log4j.appender.amqp.contentType=text/plain
* #log4j.appender.amqp.contentEncoding=null
* log4j.appender.amqp.generateId=false
* #log4j.appender.amqp.charset=null
* log4j.appender.amqp.deliveryMode=PERSISTENT
* #-------------------------------
* ## Sender configuration
* #-------------------------------
* log4j.appender.amqp.senderPoolSize=2
* log4j.appender.amqp.maxSenderRetries=30
* #log4j.appender.amqp.applicationId=null
* #-------------------------------
* ## Standard Log4J stuff
* #-------------------------------
* log4j.appender.amqp.layout=org.apache.log4j.PatternLayout
* log4j.appender.amqp.layout.ConversionPattern=%d %p %t [%c] - &lt;%m&gt;%n
* </pre>
*
* @author Jon Brisbin
* @author Gary Russell
* @author Artem Bilan
*/
public class AmqpAppender extends AppenderSkeleton {
/**
* Key name for the application id (if there is one set via the appender config) in the message properties.
*/
public static final String APPLICATION_ID = "applicationId";
/**
* Key name for the logger category name in the message properties
*/
public static final String CATEGORY_NAME = "categoryName";
/**
* Key name for the logger level name in the message properties
*/
public static final String CATEGORY_LEVEL = "level";
/**
* Name of the exchange to publish log events to.
*/
private String exchangeName = "logs";
/**
* Type of the exchange to publish log events to.
*/
private String exchangeType = "topic";
/**
* Log4J pattern format to use to generate a routing key.
*/
private String routingKeyPattern = "%c.%p";
/**
* Log4J Layout to use to generate routing key.
*/
private Layout routingKeyLayout;
/**
* Used to synchronize access to pattern layouts.
*/
private final Object layoutMutex = new Object();
/**
* Configuration arbitrary application ID.
*/
private String applicationId = null;
/**
* Where LoggingEvents are queued to send.
*/
private final LinkedBlockingQueue<Event> events = new LinkedBlockingQueue<Event>();
/**
* The pool of senders.
*/
private ExecutorService senderPool = null;
/**
* How many senders to use at once. Use more senders if you have lots of log output going through this appender.
*/
private int senderPoolSize = 2;
/**
* How many times to retry sending a message if the broker is unavailable or there is some other error.
*/
private int maxSenderRetries = 30;
/**
* Retries are delayed like: N ^ log(N), where N is the retry number.
*/
private final Timer retryTimer = new Timer("log-event-retry-delay", true);
/**
* RabbitMQ ConnectionFactory.
*/
private AbstractConnectionFactory connectionFactory;
/**
* Additional client connection properties added to the rabbit connection, with the form
* {@code key:value[,key:value]...}.
*/
private String clientConnectionProperties;
/**
* A comma-delimited list of broker addresses: host:port[,host:port]*.
* @since 1.5.6
*/
private String addresses;
/**
* RabbitMQ host to connect to.
*/
private String host = "localhost";
/**
* RabbitMQ virtual host to connect to.
*/
private String virtualHost = "/";
/**
* RabbitMQ port to connect to.
*/
private int port = 5672;
/**
* RabbitMQ user to connect as.
*/
private String username = "guest";
/**
* RabbitMQ password for this user.
*/
private String password = "guest";
/**
* Default content-type of log messages.
*/
private String contentType = "text/plain";
/**
* Default content-encoding of log messages.
*/
private String contentEncoding = null;
/**
* Whether or not to try and declare the configured exchange when this appender starts.
*/
private boolean declareExchange = false;
/**
* charset to use when converting String to byte[], default null (system default charset used).
* If the charset is unsupported on the current platform, we fall back to using
* the system charset.
*/
private String charset;
private boolean durable = true;
private MessageDeliveryMode deliveryMode = MessageDeliveryMode.PERSISTENT;
private boolean autoDelete = false;
/**
* Used to determine whether {@link MessageProperties#setMessageId(String)} is set.
*/
private boolean generateId = false;
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public void setAddresses(String addresses) {
this.addresses = addresses;
}
public String getAddresses() {
return this.addresses;
}
public String getVirtualHost() {
return this.virtualHost;
}
public void setVirtualHost(String virtualHost) {
this.virtualHost = virtualHost;
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public String getExchangeName() {
return this.exchangeName;
}
public void setExchangeName(String exchangeName) {
this.exchangeName = exchangeName;
}
public String getExchangeType() {
return this.exchangeType;
}
public void setExchangeType(String exchangeType) {
this.exchangeType = exchangeType;
}
public String getRoutingKeyPattern() {
return this.routingKeyPattern;
}
public void setRoutingKeyPattern(String routingKeyPattern) {
this.routingKeyPattern = routingKeyPattern;
}
public boolean isDeclareExchange() {
return this.declareExchange;
}
public void setDeclareExchange(boolean declareExchange) {
this.declareExchange = declareExchange;
}
public String getContentType() {
return this.contentType;
}
public void setContentType(String contentType) {
this.contentType = contentType;
}
public String getContentEncoding() {
return this.contentEncoding;
}
public void setContentEncoding(String contentEncoding) {
this.contentEncoding = contentEncoding;
}
public String getApplicationId() {
return this.applicationId;
}
public void setApplicationId(String applicationId) {
this.applicationId = applicationId;
}
public int getSenderPoolSize() {
return this.senderPoolSize;
}
public void setSenderPoolSize(int senderPoolSize) {
this.senderPoolSize = senderPoolSize;
}
public int getMaxSenderRetries() {
return this.maxSenderRetries;
}
public void setMaxSenderRetries(int maxSenderRetries) {
this.maxSenderRetries = maxSenderRetries;
}
public boolean isDurable() {
return this.durable;
}
public void setDurable(boolean durable) {
this.durable = durable;
}
public String getDeliveryMode() {
return this.deliveryMode.toString();
}
public void setDeliveryMode(String deliveryMode) {
this.deliveryMode = MessageDeliveryMode.valueOf(deliveryMode);
}
public boolean isAutoDelete() {
return this.autoDelete;
}
public void setAutoDelete(boolean autoDelete) {
this.autoDelete = autoDelete;
}
public boolean isGenerateId() {
return this.generateId;
}
public void setGenerateId(boolean generateId) {
this.generateId = generateId;
}
public String getCharset() {
return this.charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
/**
* Set additional client connection properties to be added to the rabbit connection,
* with the form {@code key:value[,key:value]...}.
* @param clientConnectionProperties the properties.
* @since 1.5.6
*/
public void setClientConnectionProperties(String clientConnectionProperties) {
this.clientConnectionProperties = clientConnectionProperties;
}
@Override
public void activateOptions() {
this.routingKeyLayout = new PatternLayout(this.routingKeyPattern
.replaceAll("%X\\{applicationId\\}", this.applicationId));
this.connectionFactory = new CachingConnectionFactory();
this.connectionFactory.setHost(this.host);
this.connectionFactory.setPort(this.port);
if (this.addresses != null) {
this.connectionFactory.setAddresses(this.addresses);
}
this.connectionFactory.setUsername(this.username);
this.connectionFactory.setPassword(this.password);
this.connectionFactory.setVirtualHost(this.virtualHost);
LogAppenderUtils.updateClientConnectionProperties(this.connectionFactory, this.clientConnectionProperties);
updateConnectionClientProperties(this.connectionFactory.getRabbitConnectionFactory().getClientProperties());
setUpExchangeDeclaration();
startSenders();
}
/**
* Subclasses can override this method to add properties to the connection client
* properties.
* @param clientProperties the client properties.
* @since 1.5.6
*/
protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
}
/**
* @deprecated - use {@link #setUpExchangeDeclaration()}
*/
@Deprecated
protected void maybeDeclareExchange() {
setUpExchangeDeclaration();
}
protected void setUpExchangeDeclaration() {
RabbitAdmin admin = new RabbitAdmin(this.connectionFactory);
if (this.declareExchange) {
Exchange x;
if ("topic".equals(this.exchangeType)) {
x = new TopicExchange(this.exchangeName, this.durable, this.autoDelete);
}
else if ("direct".equals(this.exchangeType)) {
x = new DirectExchange(this.exchangeName, this.durable, this.autoDelete);
}
else if ("fanout".equals(this.exchangeType)) {
x = new FanoutExchange(this.exchangeName, this.durable, this.autoDelete);
}
else if ("headers".equals(this.exchangeType)) {
x = new HeadersExchange(this.exchangeType, this.durable, this.autoDelete);
}
else {
x = new TopicExchange(this.exchangeName, this.durable, this.autoDelete);
}
this.connectionFactory.addConnectionListener(new DeclareExchangeConnectionListener(x, admin));
}
}
/**
* Submit the required number of senders into the pool.
*/
protected void startSenders() {
this.senderPool = Executors.newCachedThreadPool();
for (int i = 0; i < this.senderPoolSize; i++) {
this.senderPool.submit(new EventSender());
}
}
@Override
public void append(LoggingEvent event) {
this.events.add(new Event(event, event.getProperties()));
}
@Override
public void close() {
if (null != this.senderPool) {
this.senderPool.shutdownNow();
this.senderPool = null;
}
if (null != this.connectionFactory) {
this.connectionFactory.destroy();
}
this.retryTimer.cancel();
}
@Override
public boolean requiresLayout() {
return true;
}
/**
* Subclasses may modify the final message before sending.
* @param message The message.
* @param event The event.
* @return The modified message.
* @since 1.4
*/
public Message postProcessMessageBeforeSend(Message message, Event event) {
return message;
}
/**
* Helper class to actually send LoggingEvents asynchronously.
*/
protected class EventSender implements Runnable {
@Override
public void run() {
try {
RabbitTemplate rabbitTemplate = new RabbitTemplate(AmqpAppender.this.connectionFactory);
while (true) {
final Event event = AmqpAppender.this.events.take();
LoggingEvent logEvent = event.getEvent();
String name = logEvent.getLogger().getName();
Level level = logEvent.getLevel();
MessageProperties amqpProps = new MessageProperties();
amqpProps.setDeliveryMode(AmqpAppender.this.deliveryMode);
amqpProps.setContentType(AmqpAppender.this.contentType);
if (null != AmqpAppender.this.contentEncoding) {
amqpProps.setContentEncoding(AmqpAppender.this.contentEncoding);
}
amqpProps.setHeader(CATEGORY_NAME, name);
amqpProps.setHeader(CATEGORY_LEVEL, level.toString());
if (AmqpAppender.this.generateId) {
amqpProps.setMessageId(UUID.randomUUID().toString());
}
// Set applicationId, if we're using one
if (null != AmqpAppender.this.applicationId) {
amqpProps.setAppId(AmqpAppender.this.applicationId);
}
// Set timestamp
Calendar tstamp = Calendar.getInstance();
tstamp.setTimeInMillis(logEvent.getTimeStamp());
amqpProps.setTimestamp(tstamp.getTime());
// Copy properties in from MDC
@SuppressWarnings("rawtypes")
Map props = event.getProperties();
@SuppressWarnings("unchecked")
Set<Entry<?, ?>> entrySet = props.entrySet();
for (Entry<?, ?> entry : entrySet) {
amqpProps.setHeader(entry.getKey().toString(), entry.getValue());
}
LocationInfo locInfo = logEvent.getLocationInformation();
if (!"?".equals(locInfo.getClassName())) {
amqpProps.setHeader(
"location",
String.format("%s.%s()[%s]", locInfo.getClassName(), locInfo.getMethodName(),
locInfo.getLineNumber()));
}
StringBuilder msgBody;
String routingKey;
synchronized (AmqpAppender.this.layoutMutex) {
msgBody = new StringBuilder(layout.format(logEvent));
routingKey = AmqpAppender.this.routingKeyLayout.format(logEvent);
}
if (layout.ignoresThrowable() && null != logEvent.getThrowableInformation()) {
ThrowableInformation tinfo = logEvent.getThrowableInformation();
for (String line : tinfo.getThrowableStrRep()) {
msgBody.append(String.format("%s%n", line));
}
}
// Send a message
try {
Message message = null;
if (AmqpAppender.this.charset != null) {
try {
message = new Message(msgBody.toString().getBytes(AmqpAppender.this.charset), amqpProps);
}
catch (UnsupportedEncodingException e) { /* fall back to default */ }
}
if (message == null) {
message = new Message(msgBody.toString().getBytes(), amqpProps); //NOSONAR (default charset)
}
message = postProcessMessageBeforeSend(message, event);
rabbitTemplate.send(AmqpAppender.this.exchangeName, routingKey, message);
}
catch (AmqpException e) {
int retries = event.incrementRetries();
if (retries < AmqpAppender.this.maxSenderRetries) {
// Schedule a retry based on the number of times I've tried to re-send this
AmqpAppender.this.retryTimer.schedule(new TimerTask() {
@Override
public void run() {
AmqpAppender.this.events.add(event);
}
}, (long) (Math.pow(retries, Math.log(retries)) * 1000));
}
else {
errorHandler.error(
"Could not send log message " + logEvent.getRenderedMessage() + " after "
+ AmqpAppender.this.maxSenderRetries + " retries",
e, ErrorCode.WRITE_FAILURE, logEvent);
}
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
/**
* Small helper class to encapsulate a LoggingEvent, its MDC properties, and the number of retries.
*/
@SuppressWarnings("rawtypes")
protected static class Event {
private final LoggingEvent event;
private final Map properties;
private final AtomicInteger retries = new AtomicInteger(0);
public Event(LoggingEvent event, Map properties) {
this.event = event;
this.properties = properties;
}
public LoggingEvent getEvent() {
return this.event;
}
public Map getProperties() {
return this.properties;
}
public int incrementRetries() {
return this.retries.incrementAndGet();
}
}
}

View File

@@ -1,4 +0,0 @@
/**
* Provides classes supporting Log4J appenders.
*/
package org.springframework.amqp.rabbit.log4j;

View File

@@ -1,8 +1,2 @@
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.0.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.1.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.1.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.2.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.2.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.3.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.3.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.4.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.4.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.5.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.5.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-1.6.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.6.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-1.6.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit-2.0.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-2.0.xsd
http\://www.springframework.org/schema/rabbit/spring-rabbit.xsd=org/springframework/amqp/rabbit/config/spring-rabbit-2.0.xsd

View File

@@ -1,771 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.org/schema/rabbit" xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tool="http://www.springframework.org/schema/tool"
xmlns:beans="http://www.springframework.org/schema/beans" targetNamespace="http://www.springframework.org/schema/rabbit"
elementFormDefault="qualified" attributeFormDefault="unqualified">
<xsd:import namespace="http://www.springframework.org/schema/beans" />
<xsd:import namespace="http://www.springframework.org/schema/tool" />
<xsd:element name="queue">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a queue for consumers to retrieve messages. Uses an existing queue
with the same name if it exists on the broker, or else declares a
new one. If you want to send a message use an exchange.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element ref="queue-arguments" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The id of the queue in case it is different than the name. Clients can receive or listen for messages by referring to the
queue itself, or to its name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the queue. Clients can receive or listen for messages by referring to the
queue itself, or to its name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-delete" use="optional" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag indicating that an queue will be deleted when it is no longer in use, i.e. the connection that declared it is closed. Default is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="exclusive" use="optional" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag indicating that the queue is exclusive to this connection. Default is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="durable" use="optional" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag indicating that the queue is durable, meaning that it will survive broker restarts (not that the messages in it will, although they might if they are persistent). Default is true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="queue-arguments" type="mapType">
<xsd:annotation>
<xsd:documentation source="java:java.util.Map"><![CDATA[
A Map to pass to the broker when this component is declared.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="direct-exchange">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a direct exchange for producers to send messages to. Uses an existing exchange
with the same name if it exists on the broker, or declares a
new one. A direct exhange routes messages
to queues that are bound to the exchange when the routing key in the message
matches that in the binding exactly. You can set up bindings here too, either with
explicit routing keys, or using the queue name implicitly.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="exchangeType">
<xsd:sequence>
<xsd:element name="bindings" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Groups bindings of queues to this exchange.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice>
<xsd:element name="binding" maxOccurs="unbounded" type="directBindingType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Declares a binding of a queue to this exchange either with
an explicit routing key, or using the queue name implicitly.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="topic-exchange">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a topic exchange for producers to send messages to. Uses an existing exchange
with the same name if it exists on the broker, or declares a
new one. A topic exhange routes messages
to queues that are bound to the exchange when the routing key in the message
matches the routing pattern in the binding of the queue.
You can set up bindings here too.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="exchangeType">
<xsd:sequence>
<xsd:element name="bindings" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Groups bindings of queues to this exchange.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice>
<xsd:element name="binding" maxOccurs="unbounded" type="topicBindingType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Declares a binding of a queue to this exchange either with
a routing pattern, e.g. "uk.weather.*" or "uk.#".
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="fanout-exchange">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a fanout exchange for producers to send messages to. Uses an existing exchange
with the same name if it exists on the broker, or declares a
new one. A fanout exhange routes messages
to all queues that are bound to the exchange. You can set up bindings here too.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="exchangeType">
<xsd:sequence>
<xsd:element name="bindings" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Groups bindings of queues to this exchange.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice>
<xsd:element name="binding" maxOccurs="unbounded" type="bindingType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Binds a queue to this exchange. All messages sent to this exchange will be
placed on this queue by the broker.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:element name="headers-exchange">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a headers exchange for producers to send messages to. Uses an existing exchange
with the same name if it exists on the broker, or declares a
new one. A headers exhange routes messages
to all queues where a message header matches that specified in the binding of the queue.
You can set up bindings here too.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:complexContent>
<xsd:extension base="exchangeType">
<xsd:sequence>
<xsd:element name="bindings" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation><![CDATA[
Groups bindings of queues to this exchange.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:choice>
<xsd:element name="binding" maxOccurs="unbounded" type="headersBindingType">
<xsd:annotation>
<xsd:documentation><![CDATA[
Binds a queue to this exchange. Messages sent to this exchange will be
placed on this queue by the broker if they contain a header that matches
this binding (key-value pair).
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="exchangeType">
<xsd:sequence>
<xsd:element ref="exchange-arguments" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The id of the exchange bean definition in case it is different than the name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="name" use="required" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the exchange. Clients can send a message by referring to the
exchange itself, or to its name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-delete" use="optional" type="xsd:string" default="false">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag indicating that an exchange will be deleted when no longer in use, i.e. the connection that declared it is closed. Default is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="durable" use="optional" type="xsd:string " default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag indicating that the exchange is durable, i.e. will survive broker restart. Default is true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="exchange-arguments" type="mapType">
<xsd:annotation>
<xsd:documentation source="java:java.util.Map"><![CDATA[
A Map to pass to the broker when this component is declared.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="directBindingType">
<xsd:complexContent>
<xsd:extension base="bindingType">
<xsd:attribute name="key" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
An explicit routing key binding the queue to this exchange.
If not provided defaults to the queue name.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="topicBindingType">
<xsd:complexContent>
<xsd:extension base="bindingType">
<xsd:attribute name="pattern" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
An explicit routing pattern binding the queue to this exchange. In the pattern,
the symbol # matches one or more words and the symbol * matches any single word.
Typical bindings might be "uk.#" for all items in the uk, "#.weather" for all
weather items, or "uk.weather" for all uk weather items.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="headersBindingType">
<xsd:complexContent>
<xsd:extension base="bindingType">
<xsd:attribute name="key" use="required" />
<xsd:attribute name="value" use="required" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="bindingType">
<xsd:sequence>
<xsd:element ref="binding-arguments" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
<xsd:attribute name="queue" use="required">
<xsd:annotation>
<xsd:documentation source="java:org.springframework.amqp.core.Queue"><![CDATA[
The bean name of the Queue to bind to this exchange.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.amqp.core.Queue" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="binding-arguments" type="mapType">
<xsd:annotation>
<xsd:documentation source="java:java.util.Map"><![CDATA[
A Map to pass to the broker when this component is declared.
]]></xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:complexType name="mapType">
<xsd:complexContent>
<xsd:extension base="beans:mapType">
<xsd:attribute name="ref" use="optional">
<xsd:annotation>
<xsd:documentation source="java:java.util.Map"><![CDATA[
The bean name of the Map to pass to the broker when this component is declared.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.Map" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:element name="listener-container">
<xsd:annotation>
<xsd:documentation><![CDATA[
Each listener child element will be hosted by a container whose configuration
is determined by this parent element. This variant builds RabbitMQ
listener containers, operating against a specified ConnectionFactory.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation>
<tool:exports type="org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Optional bean id for the container.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connection-factory" type="xsd:string" default="rabbitConnectionFactory">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the org.springframework.amqp.rabbit.connection.ConnectionFactory.
Default referenced bean name is "rabbitConnectionFactory".
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.amqp.rabbit.connection.ConnectionFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="task-executor" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to a Spring TaskExecutor (or standard JDK 1.5 Executor) for executing
listener invokers. Default is a SimpleAsyncTaskExecutor, using internally managed threads.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="java.util.concurrent.Executor" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converter" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the MessageConverter strategy for converting AMQP Messages to
listener method arguments for any referenced 'listener' that is a POJO.
Default is a SimpleMessageConverter.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.amqp.support.converter.MessageConverter" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="error-handler" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to an ErrorHandler strategy for handling any uncaught Exceptions
that may occur during the execution of the MessageListener.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.util.ErrorHandler" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="acknowledge" default="auto">
<xsd:annotation>
<xsd:documentation><![CDATA[
The acknowledge mode: "auto", "manual", or "none".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="auto" />
<xsd:enumeration value="manual" />
<xsd:enumeration value="none" />
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="transaction-manager" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to an external PlatformTransactionManager.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.transaction.PlatformTransactionManager" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="concurrency" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The number of concurrent consumers to start for each listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="prefetch" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Tells the broker how many messages to send to each consumer in a single request. Often this can be set quite high
to improve throughput. It should be greater than or equal to the transaction size.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="transaction-size" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Tells the container how many messages to process in a single transaction (if the channel is transactional). For
best results it should be less than or equal to the prefetch count.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="phase" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The lifecycle phase within which this container should start and stop. The lower
the value the earlier this container will start and the later it will stop. The
default is Integer.MAX_VALUE meaning the container will start as late as possible
and stop as soon as possible.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-startup" type="xsd:string" default="true">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag to indicate that the container should start up automatically when the enclosing context is refreshed. Default true.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="advice-chain" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to a chain of AOP advice to be applied to the listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="listenerType">
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The unique identifier for this listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queue-names" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The queue names for this listener as a comma-separated list. Either this or queues is required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queues" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The queues (bean references) for this listener as a comma-separated list. Either this or queue-names is required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The bean name of the listener object, implementing
the MessageListener/ChannelAwareMessageListener interface
or defining the specified listener method. Required.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref" />
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the listener method to invoke. If not specified,
the target bean is supposed to implement the MessageListener
or ChannelAwareMessageListener interface.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="response-exchange" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the default response Exchange to send response messages to.
This will be applied in case of a request message that does not carry
a "replyTo" property. Note: This only applies to a listener method with
a return value, for which each result object will be converted into a
response message.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="response-routing-key" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The routing key to send along with a response message.
This will be applied in case of a request message that does not carry
a "replyTo" property. Note: This only applies to a listener method with
a return value, for which each result object will be converted into a
response message.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
<xsd:element name="admin">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a rabbit admin (org.springframework.amqp.rabbit.core.RabbitAdmin)
for customers to manage exchanges, queues and bindings.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Unique name for this rabbit admin used as a bean definition identifier.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connection-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to rabbit connection factory. Either 'connection-factory' or
'template' attribute can be set.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.amqp.rabbit.connection.ConnectionFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="auto-startup" type="xsd:boolean">
<xsd:annotation>
<xsd:documentation><![CDATA[
Specifies if the queues, exchanges and bindings in the context should be automatically declared (lazily on first connection to the broker). Default value is 'true'.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="template">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a rabbit template (org.springframework.amqp.rabbit.core.RabbitTemplate)
for convenient access to the broker.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Unique name for this rabbit template used as a bean definition identifier.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="routing-key" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Default routing key for sending messages. Default is empty.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="exchange" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Default exchange for sending messages. Default is empty (the default exchange).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="queue" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Default queue for receiving messages. Default is empty (non-existent queue).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="reply-timeout" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Timeout for send and receive in milliseconds. Default is 5000 (5 seconds).
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="channel-transacted" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Flag to indicate that the channel should be used transactionally. Default is false.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="encoding" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Encoding to use for packing and unpacking MessagePoperties of type String. Default is UTF-8.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converter" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
MessageConverter to convert between raw bytes and Java objects in the *convert* methods. Defaults to a simple implementation that handles Strings, byte arrays and Serializable.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.amqp.support.converter.MessageConverter" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connection-factory" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to rabbit connection factory.
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.amqp.rabbit.connection.ConnectionFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="connection-factory">
<xsd:annotation>
<xsd:documentation><![CDATA[
Creates a rabbit CachingConnectionFactory with sensible defaults.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="id" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Unique name for this rabbit connection factory used as a bean definition identifier.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="host" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Hostname to connect to broker. Default is "localhost".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="port" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Port number to connect to broker. Default is 5672.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="username" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Username to connect to broker. Default is "guest".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="password" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Password to connect to broker. Default is "guest".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="virtual-host" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Virtual host name to connect to broker. Default is "/". Virtual hosts are logical partitions of the broker with separate queues, excchanges, users, etc.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="channel-cache-size" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Cache size for channels. More channels can be used by clients, but in excess of this number they will not be cached.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="connection-factory" type="xsd:string" use="optional">
<xsd:annotation>
<xsd:documentation><![CDATA[
Reference to native rabbit connection factory, where you can specify native features like heartbeat. The other properties (host, port) etc. on this element
override the ones on the native connection factory (which is used as a parent bean definition).
]]></xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="com.rabbitmq.client.ConnectionFactory" />
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -1,220 +0,0 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.log4j;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.util.Collection;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.xml.parsers.DocumentBuilderFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.Logger;
import org.apache.log4j.MDC;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.test.BrokerRunning;
import org.springframework.amqp.utils.test.TestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
* @author Jon Brisbin
* @author Gary Russell
* @author Gunnar Hillert
* @author Artem Bilan
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "org.springframework.amqp.rabbit.log4j" }, loader = AnnotationConfigContextLoader.class)
@DirtiesContext
public class AmqpAppenderIntegrationTests {
@Rule
public BrokerRunning brokerIsRunning = BrokerRunning.isRunning();
@Autowired
private ApplicationContext applicationContext;
private Logger log;
private SimpleMessageListenerContainer listenerContainer;
@SuppressWarnings("deprecation") // SF 4.2.1
@Before
public void setUp() throws Exception {
org.springframework.util.Log4jConfigurer.initLogging("classpath:log4j-amqp.properties");
log = Logger.getLogger(getClass());
listenerContainer = applicationContext.getBean(SimpleMessageListenerContainer.class);
}
@After
public void tearDown() {
listenerContainer.shutdown();
}
@SuppressWarnings("deprecation") // SF 4.2.1
@AfterClass
public static void reset() throws Exception {
org.springframework.util.Log4jConfigurer.initLogging("classpath:log4j.properties");
}
@Test
public void testAppender() throws InterruptedException {
TestListener testListener = (TestListener) applicationContext.getBean("testListener", 4);
listenerContainer.setMessageListener(testListener);
listenerContainer.start();
AmqpAppender appender = (AmqpAppender) log.getParent().getAllAppenders().nextElement();
assertFalse(appender.isDurable());
assertEquals(MessageDeliveryMode.NON_PERSISTENT.toString(), appender.getDeliveryMode());
Logger log = Logger.getLogger(getClass());
log.debug("This is a DEBUG message");
log.info("This is an INFO message");
log.warn("This is a WARN message");
log.error("This is an ERROR message", new RuntimeException("Test exception"));
assertTrue(testListener.getLatch().await(5, TimeUnit.SECONDS));
assertNotNull(testListener.getId());
assertThat(TestUtils.getPropertyValue(appender, "connectionFactory.connectionListener.delegates",
Collection.class).size(), equalTo(1));
}
@Test
public void testAppenderWithProps() throws InterruptedException {
TestListener testListener = (TestListener) applicationContext.getBean("testListener", 4);
listenerContainer.setMessageListener(testListener);
listenerContainer.start();
String propertyName = "someproperty";
String propertyValue = "property.value";
MDC.put(propertyName, propertyValue);
log.debug("This is a DEBUG message with properties");
log.info("This is an INFO message with properties");
log.warn("This is a WARN message with properties");
log.error("This is an ERROR message with properties", new RuntimeException("Test exception"));
MDC.remove(propertyName);
assertTrue(testListener.getLatch().await(5, TimeUnit.SECONDS));
MessageProperties messageProperties = testListener.getMessageProperties();
assertNotNull(messageProperties);
assertNotNull(messageProperties.getHeaders().get(propertyName));
assertEquals(propertyValue, messageProperties.getHeaders().get(propertyName));
assertEquals("bar", messageProperties.getHeaders().get("foo"));
}
@Test
public void testCharset() throws InterruptedException {
Logger packageLogger = Logger.getLogger("org.springframework.amqp.rabbit.log4j");
AmqpAppender appender = (AmqpAppender) packageLogger.getAppender("amqp");
assertEquals("UTF-8", appender.getCharset());
TestListener testListener = (TestListener) applicationContext.getBean("testListener", 1);
listenerContainer.setMessageListener(testListener);
listenerContainer.start();
String foo = "\u0fff"; // UTF-8 -> 0xe0bfbf
log.info(foo);
assertTrue(testListener.getLatch().await(5, TimeUnit.SECONDS));
byte[] body = testListener.getMessage().getBody();
int lineSeparatorExtraBytes = System.getProperty("line.separator").getBytes().length - 1;
assertEquals(0xe0, body[body.length - 5 - lineSeparatorExtraBytes] & 0xff);
assertEquals(0xbf, body[body.length - 4 - lineSeparatorExtraBytes] & 0xff);
assertEquals(0xbf, body[body.length - 3 - lineSeparatorExtraBytes] & 0xff);
}
@Test
public void testIgnoresThrowableWithCustomLayout() throws Exception {
Logger customLayoutLogger = Logger.getLogger("org.springframework.amqp.rabbit.logging.customLayout");
TestListener testListener = (TestListener) applicationContext.getBean("testListener", 1);
listenerContainer.setMessageListener(testListener);
listenerContainer.start();
customLayoutLogger.error("This is an ERROR message", new RuntimeException("Test exception"));
assertTrue(testListener.getLatch().await(5, TimeUnit.SECONDS));
assertNotNull(testListener.getId());
//This code parses an XML and ends up with exception without the general fix for AMQP-363
DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new ByteArrayInputStream(testListener.getMessage().getBody()));
listenerContainer.destroy();
}
/*
* When running as main(); should shutdown cleanly.
*/
@SuppressWarnings("deprecation") // SF 4.2.1
public static void main(String[] args) throws Exception {
org.springframework.util.Log4jConfigurer.initLogging("classpath:log4j-amqp.properties");
Log logger = LogFactory.getLog(AmqpAppenderIntegrationTests.class);
logger.info("foo");
Thread.sleep(1000);
org.springframework.util.Log4jConfigurer.shutdownLogging();
}
public static class EnhancedAppender extends AmqpAppender {
private String foo;
@Override
public Message postProcessMessageBeforeSend(Message message, Event event) {
message.getMessageProperties().setHeader("foo", this.foo);
return message;
}
public String getFoo() {
return this.foo;
}
public void setFoo(String foo) {
this.foo = foo;
}
@Override
protected void updateConnectionClientProperties(Map<String, Object> clientProperties) {
assertEquals("bar", clientProperties.get("foo"));
assertEquals("qux", clientProperties.get("baz"));
clientProperties.put("foo", this.foo.toUpperCase());
}
}
}

View File

@@ -1,36 +0,0 @@
/*
* Copyright 2011-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.amqp.rabbit.log4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextLoader;
/**
* @author Jon Brisbin
*/
public class AnnotationConfigContextLoader implements ContextLoader {
@Override
public String[] processLocations(Class<?> clazz, String... locations) {
return locations;
}
@Override
public ApplicationContext loadContext(String... locations) throws Exception {
return new AnnotationConfigApplicationContext(locations);
}
}

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.amqp.rabbit.log4j;
package org.springframework.amqp.rabbit.logback;
import javax.annotation.PreDestroy;
@@ -37,10 +37,6 @@ import org.springframework.context.annotation.Scope;
@Configuration
public class AmqpAppenderConfiguration {
static {
// DOMConfigurator.configure(AmqpAppenderTests.class.getResource("/log4j.xml"));
}
private static final String QUEUE = "amqp.appender.test";
private static final String EXCHANGE = "logs";

View File

@@ -37,8 +37,6 @@ import org.slf4j.MDC;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
import org.springframework.amqp.rabbit.log4j.AmqpAppenderConfiguration;
import org.springframework.amqp.rabbit.log4j.TestListener;
import org.springframework.amqp.rabbit.test.BrokerRunning;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package org.springframework.amqp.rabbit.log4j;
package org.springframework.amqp.rabbit.logback;
import java.util.concurrent.CountDownLatch;

View File

@@ -7,6 +7,230 @@ See <<whats-new>>.
[[previous-whats-new]]
=== Previous Releases
==== Changes in 1.6 Since 1.5
===== Testing Support
A new testing support library is now provided.
See <<testing>> for more information.
===== Builder
Builders are now available providing a fluent API for configuring `Queue` and `Exchange` objects.
See <<builder-api>> for more information.
===== Namespace Changes
====== Connection Factory
It is now possible to add a `thread-factory` to a connection factory bean declaration, for example to name the threads
created by the `amqp-client` library.
See <<connections>> for more information.
When using `CacheMode.CONNECTION`, you can now limit the total number of connections allowed.
See <<connections>> for more information.
====== Queue Definitions
It is now possible to provide a naming strategy for anonymous queues; see <<anonymous-queue>> for more information.
===== Listener Container Changes
====== Idle Message Listener Detection
It is now possible to configure listener containers to publish `ApplicationEvent` s when idle.
See <<idle-containers>> for more information.
====== Mismatched Queue Detection
By default, when a listener container starts, if queues with mismatched properties or arguments were detected,
the container would log the exception but continue to listen.
The container now has a property `mismatchedQueuesFatal` which will prevent the container (and context) from
starting if the problem is detected during startup.
It will also stop the container if the problem is detected later, such as after recovering from a connection failure.
See <<containerAttributes>> for more information.
====== Listener Container Logging
Now listener container provides its `beanName` into the internal `SimpleAsyncTaskExecutor` as a `threadNamePrefix`.
It is useful for logs analysis.
====== Default Error Handler
The default error handler (`ConditionalRejectingErrorHandler`) now considers irrecoverable `@RabbitListener`
exceptions as fatal.
See <<exception-handling>> for more information.
===== AutoDeclare and RabbitAdmins
See <<containerAttributes>> (`autoDeclare`) for some changes to the semantics of that option with respect to the use
of `RabbitAdmin` s in the application context.
===== AmqpTemplate: receive with timeout
A number of new `receive()` methods with `timeout` have been introduced for the `AmqpTemplate`
and its `RabbitTemplate` implementation.
See <<polling-consumer>> for more information.
===== AsyncRabbitTemplate
A new `AsyncRabbitTemplate` has been introduced.
This template provides a number of send and receive methods, where the return value is a `ListenableFuture`, which can
be used later to obtain the result either synchronously, or asynchronously.
See <<async-template>> for more information.
===== RabbitTemplate Changes
1.4.1 introduced the ability to use https://www.rabbitmq.com/direct-reply-to.html[Direct reply-to] when the broker
supports it; it is more efficient than using a temporary queue for each reply.
This version allows you to override this default behavior and use a temporary queue by setting the
`useTemporaryReplyQueues` property to `true`.
See <<direct-reply-to>> for more information.
The `RabbitTemplate` now supports a `user-id-expression` (`userIdExpression` when using Java configuration).
See https://www.rabbitmq.com/validated-user-id.html[Validated User-ID RabbitMQ documentation] and <<template-user-id>> for more information.
===== Message Properties
====== CorrelationId
The `correlationId` message property can now be a `String`.
See <<message-properties-converters>> for more information.
====== Long String Headers
Previously, the `DefaultMessagePropertiesConverter` "converted" headers longer than the long string limit (default 1024)
to a `DataInputStream` (actually it just referenced the `LongString`'s `DataInputStream`).
On output, this header was not converted (except to a String, e.g. `java.io.DataInputStream@1d057a39` by calling
`toString()` on the stream).
With this release, long `LongString` s are now left as `LongString` s by default; you can access the contents via
the `getBytes[]`, `toString()`, or `getStream()` methods.
A large incoming `LongString` is now correctly "converted" on output too.
See <<message-properties-converters>> for more information.
====== Inbound Delivery Mode
The `deliveryMode` property is no longer mapped to the `MessageProperties.deliveryMode`; this is to avoid unintended
propagation if the the same `MessageProperties` object is used to send an outbound message.
Instead, the inbound `deliveryMode` header is mapped to `MessageProperties.receivedDeliveryMode`.
See <<message-properties-converters>> for more information.
When using annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_DELIVERY_MODE`.
See <<async-annotation-driven-enable-signature>> for more information.
====== Inbound User ID
The `user_id` property is no longer mapped to the `MessageProperties.userId`; this is to avoid unintended
propagation if the the same `MessageProperties` object is used to send an outbound message.
Instead, the inbound `userId` header is mapped to `MessageProperties.receivedUserId`.
See <<message-properties-converters>> for more information.
When using annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_USER_ID`.
See <<async-annotation-driven-enable-signature>> for more information.
===== RabbitAdmin Changes
====== Declaration Failures
Previously, the `ignoreDeclarationFailures` flag only took effect for `IOException` on the channel (such as mis-matched
arguments).
It now takes effect for any exception (such as `TimeoutException`).
In addition, a `DeclarationExceptionEvent` is now published whenever a declaration fails.
The `RabbitAdmin` last declaration event is also available as a property `lastDeclarationExceptionEvent`.
See <<broker-configuration>> for more information.
===== @RabbitListener Changes
====== Multiple Containers per Bean
When using Java 8 or later, it is now possible to add multiple `@RabbitListener` annotations to `@Bean` classes or
their methods.
When using Java 7 or earlier, you can use the `@RabbitListeners` container annotation to provide the same
functionality.
See <<repeatable-rabbit-listener>> for more information.
====== @SendTo SpEL Expressions
`@SendTo` for routing replies with no `replyTo` property can now be SpEL expressions evaluated against the
request/reply.
See <<async-annotation-driven-reply>> for more information.
====== @QueueBinding Improvements
You can now specify arguments for queues, exchanges and bindings in `@QueueBinding` annotations.
Header exchanges are now supported by `@QueueBinding`.
See <<async-annotation-driven>> for more information.
===== Delayed Message Exchange
Spring AMQP now has first class support for the RabbitMQ Delayed Message Exchange plugin.
See <<delayed-message-exchange>> for more information.
===== Exchange internal flag
Any `Exchange` definitions can now be marked as `internal` and the `RabbitAdmin` will pass the value to the broker when
declaring the exchange.
See <<broker-configuration>> for more information.
===== CachingConnectionFactory Changes
====== CachingConnectionFactory Cache Statistics
The `CachingConnectionFactory` now provides cache properties at runtime and over JMX.
See <<runtime-cache-properties>> for more information.
====== Access the Underlying RabbitMQ Connection Factory
A new getter has been added to provide access to the underlying factory.
This can be used, for example, to add custom connection properties.
See <<custom-client-props>> for more information.
====== Channel Cache
The default channel cache size has been increased from 1 to 25.
See <<connections>> for more information.
In addition, the `SimpleMessageListenerContainer` no longer adjusts the cache size to be at least as large as the number
of `concurrentConsumers` - this was superfluous, since the container consumer channels are never cached.
===== RabbitConnectionFactoryBean
The factory bean now exposes a property to add client connection properties to connections made by the resulting
factory.
===== Java Deserialization
A "white list" of allowable classes can now be configured when using Java deserialization.
It is important to consider creating a white list if you accept messages with serialized java objects from
untrusted sources.
See <<java-deserialization>> for more information.
===== JSON MessageConverter
Improvements to the JSON message converter now allow the consumption of messages that don't have type information
in message headers.
See <<async-annotation-conversion>> and <<json-message-converter>> for more information.
===== Logging Appenders
====== Log4j2
A log4j2 appender has been added, and the appenders can now be configured with an `addresses` property to connect
to a broker cluster.
====== Client Connection Properties
You can now add custom client connection properties to RabbitMQ connections.
See <<logging>> for more information.
==== Changes in 1.5 Since 1.4
===== spring-erlang is No Longer Supported

View File

@@ -3,7 +3,6 @@
The framework provides logging appenders for several popular logging subsystems:
- log4j (since Spring AMQP _version 1.1_)
- logback (since Spring AMQP _version 1.4_)
- log4j2 (since Spring AMQP _version 1.6_)
@@ -111,24 +110,6 @@ If the charset is unsupported on the current platform, we fall back to using the
|===
==== Log4j Appender
.Example log4j.properties Snippet
[source, text]
----
log4j.appender.amqp.addresses=foo:5672,bar:5672
log4j.appender.amqp=org.springframework.amqp.rabbit.log4j.AmqpAppender
log4j.appender.amqp.applicationId=myApplication
log4j.appender.amqp.routingKeyPattern=%X{applicationId}.%c.%p
log4j.appender.amqp.layout=org.apache.log4j.PatternLayout
log4j.appender.amqp.layout.ConversionPattern=%d %p %t [%c] - <%m>%n
log4j.appender.amqp.generateId=true
log4j.appender.amqp.charset=UTF-8
log4j.appender.amqp.durable=false
log4j.appender.amqp.deliveryMode=NON_PERSISTENT
log4j.appender.amqp.declareExchange=true
----
==== Log4j2 Appender
.Example log4j2.xml Snippet

View File

@@ -1,229 +1,13 @@
[[whats-new]]
=== What's New
==== Changes in 1.6 Since 1.5
==== Changes in 2.0 Since 1.6
===== Testing Support
===== Log4j Appender
A new testing support library is now provided.
See <<testing>> for more information.
This appender is no longer available due to the end-of-life of log4j.
See <<logging>> for information about the available log appenders.
===== Builder
Builders are now available providing a fluent API for configuring `Queue` and `Exchange` objects.
See <<builder-api>> for more information.
===== Namespace Changes
====== Connection Factory
It is now possible to add a `thread-factory` to a connection factory bean declaration, for example to name the threads
created by the `amqp-client` library.
See <<connections>> for more information.
When using `CacheMode.CONNECTION`, you can now limit the total number of connections allowed.
See <<connections>> for more information.
====== Queue Definitions
It is now possible to provide a naming strategy for anonymous queues; see <<anonymous-queue>> for more information.
===== Listener Container Changes
====== Idle Message Listener Detection
It is now possible to configure listener containers to publish `ApplicationEvent` s when idle.
See <<idle-containers>> for more information.
====== Mismatched Queue Detection
By default, when a listener container starts, if queues with mismatched properties or arguments were detected,
the container would log the exception but continue to listen.
The container now has a property `mismatchedQueuesFatal` which will prevent the container (and context) from
starting if the problem is detected during startup.
It will also stop the container if the problem is detected later, such as after recovering from a connection failure.
See <<containerAttributes>> for more information.
====== Listener Container Logging
Now listener container provides its `beanName` into the internal `SimpleAsyncTaskExecutor` as a `threadNamePrefix`.
It is useful for logs analysis.
====== Default Error Handler
The default error handler (`ConditionalRejectingErrorHandler`) now considers irrecoverable `@RabbitListener`
exceptions as fatal.
See <<exception-handling>> for more information.
===== AutoDeclare and RabbitAdmins
See <<containerAttributes>> (`autoDeclare`) for some changes to the semantics of that option with respect to the use
of `RabbitAdmin` s in the application context.
===== AmqpTemplate: receive with timeout
A number of new `receive()` methods with `timeout` have been introduced for the `AmqpTemplate`
and its `RabbitTemplate` implementation.
See <<polling-consumer>> for more information.
===== AsyncRabbitTemplate
A new `AsyncRabbitTemplate` has been introduced.
This template provides a number of send and receive methods, where the return value is a `ListenableFuture`, which can
be used later to obtain the result either synchronously, or asynchronously.
See <<async-template>> for more information.
===== RabbitTemplate Changes
1.4.1 introduced the ability to use https://www.rabbitmq.com/direct-reply-to.html[Direct reply-to] when the broker
supports it; it is more efficient than using a temporary queue for each reply.
This version allows you to override this default behavior and use a temporary queue by setting the
`useTemporaryReplyQueues` property to `true`.
See <<direct-reply-to>> for more information.
The `RabbitTemplate` now supports a `user-id-expression` (`userIdExpression` when using Java configuration).
See https://www.rabbitmq.com/validated-user-id.html[Validated User-ID RabbitMQ documentation] and <<template-user-id>> for more information.
===== Message Properties
====== CorrelationId
The `correlationId` message property can now be a `String`.
See <<message-properties-converters>> for more information.
====== Long String Headers
Previously, the `DefaultMessagePropertiesConverter` "converted" headers longer than the long string limit (default 1024)
to a `DataInputStream` (actually it just referenced the `LongString`'s `DataInputStream`).
On output, this header was not converted (except to a String, e.g. `java.io.DataInputStream@1d057a39` by calling
`toString()` on the stream).
With this release, long `LongString` s are now left as `LongString` s by default; you can access the contents via
the `getBytes[]`, `toString()`, or `getStream()` methods.
A large incoming `LongString` is now correctly "converted" on output too.
See <<message-properties-converters>> for more information.
====== Inbound Delivery Mode
The `deliveryMode` property is no longer mapped to the `MessageProperties.deliveryMode`; this is to avoid unintended
propagation if the the same `MessageProperties` object is used to send an outbound message.
Instead, the inbound `deliveryMode` header is mapped to `MessageProperties.receivedDeliveryMode`.
See <<message-properties-converters>> for more information.
When using annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_DELIVERY_MODE`.
See <<async-annotation-driven-enable-signature>> for more information.
====== Inbound User ID
The `user_id` property is no longer mapped to the `MessageProperties.userId`; this is to avoid unintended
propagation if the the same `MessageProperties` object is used to send an outbound message.
Instead, the inbound `userId` header is mapped to `MessageProperties.receivedUserId`.
See <<message-properties-converters>> for more information.
When using annotated endpoints, the header is provided in the header named `AmqpHeaders.RECEIVED_USER_ID`.
See <<async-annotation-driven-enable-signature>> for more information.
===== RabbitAdmin Changes
====== Declaration Failures
Previously, the `ignoreDeclarationFailures` flag only took effect for `IOException` on the channel (such as mis-matched
arguments).
It now takes effect for any exception (such as `TimeoutException`).
In addition, a `DeclarationExceptionEvent` is now published whenever a declaration fails.
The `RabbitAdmin` last declaration event is also available as a property `lastDeclarationExceptionEvent`.
See <<broker-configuration>> for more information.
===== @RabbitListener Changes
====== Multiple Containers per Bean
When using Java 8 or later, it is now possible to add multiple `@RabbitListener` annotations to `@Bean` classes or
their methods.
When using Java 7 or earlier, you can use the `@RabbitListeners` container annotation to provide the same
functionality.
See <<repeatable-rabbit-listener>> for more information.
====== @SendTo SpEL Expressions
`@SendTo` for routing replies with no `replyTo` property can now be SpEL expressions evaluated against the
request/reply.
See <<async-annotation-driven-reply>> for more information.
====== @QueueBinding Improvements
You can now specify arguments for queues, exchanges and bindings in `@QueueBinding` annotations.
Header exchanges are now supported by `@QueueBinding`.
See <<async-annotation-driven>> for more information.
===== Delayed Message Exchange
Spring AMQP now has first class support for the RabbitMQ Delayed Message Exchange plugin.
See <<delayed-message-exchange>> for more information.
===== Exchange internal flag
Any `Exchange` definitions can now be marked as `internal` and the `RabbitAdmin` will pass the value to the broker when
declaring the exchange.
See <<broker-configuration>> for more information.
===== CachingConnectionFactory Changes
====== CachingConnectionFactory Cache Statistics
The `CachingConnectionFactory` now provides cache properties at runtime and over JMX.
See <<runtime-cache-properties>> for more information.
====== Access the Underlying RabbitMQ Connection Factory
A new getter has been added to provide access to the underlying factory.
This can be used, for example, to add custom connection properties.
See <<custom-client-props>> for more information.
====== Channel Cache
The default channel cache size has been increased from 1 to 25.
See <<connections>> for more information.
In addition, the `SimpleMessageListenerContainer` no longer adjusts the cache size to be at least as large as the number
of `concurrentConsumers` - this was superfluous, since the container consumer channels are never cached.
===== RabbitConnectionFactoryBean
The factory bean now exposes a property to add client connection properties to connections made by the resulting
factory.
===== Java Deserialization
A "white list" of allowable classes can now be configured when using Java deserialization.
It is important to consider creating a white list if you accept messages with serialized java objects from
untrusted sources.
See <<java-deserialization>> for more information.
===== JSON MessageConverter
Improvements to the JSON message converter now allow the consumption of messages that don't have type information
in message headers.
See <<async-annotation-conversion>> and <<json-message-converter>> for more information.
===== Logging Appenders
====== Log4j2
A log4j2 appender has been added, and the appenders can now be configured with an `addresses` property to connect
to a broker cluster.
====== Client Connection Properties
You can now add custom client connection properties to RabbitMQ connections.
See <<logging>> for more information.
==== Earlier Releases