Java tutorial
/* * Copyright 2002-2019 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 * * https://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.connection; import java.io.IOException; import java.net.InetAddress; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.security.GeneralSecurityException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.amqp.rabbit.support.RabbitExceptionTranslator; import org.springframework.beans.factory.BeanNameAware; import org.springframework.beans.factory.DisposableBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisherAware; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextClosedEvent; import org.springframework.lang.Nullable; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; import com.rabbitmq.client.Address; import com.rabbitmq.client.BlockedListener; import com.rabbitmq.client.Recoverable; import com.rabbitmq.client.RecoveryListener; import com.rabbitmq.client.impl.recovery.AutorecoveringConnection; /** * @author Dave Syer * @author Gary Russell * @author Steve Powell * @author Artem Bilan * @author Will Droste * */ public abstract class AbstractConnectionFactory implements ConnectionFactory, DisposableBean, BeanNameAware, ApplicationContextAware, ApplicationEventPublisherAware, ApplicationListener<ContextClosedEvent> { private static final String PUBLISHER_SUFFIX = ".publisher"; public static final int DEFAULT_CLOSE_TIMEOUT = 30000; private static final String BAD_URI = "setUri() was passed an invalid URI; it is ignored"; protected final Log logger = LogFactory.getLog(getClass()); // NOSONAR private final com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory; private final CompositeConnectionListener connectionListener = new CompositeConnectionListener(); private final CompositeChannelListener channelListener = new CompositeChannelListener(); private final AtomicInteger defaultConnectionNameStrategyCounter = new AtomicInteger(); private AbstractConnectionFactory publisherConnectionFactory; private RecoveryListener recoveryListener = new RecoveryListener() { @Override public void handleRecoveryStarted(Recoverable recoverable) { if (AbstractConnectionFactory.this.logger.isDebugEnabled()) { AbstractConnectionFactory.this.logger.debug("Connection recovery started: " + recoverable); } } @Override public void handleRecovery(Recoverable recoverable) { if (AbstractConnectionFactory.this.logger.isDebugEnabled()) { AbstractConnectionFactory.this.logger.debug("Connection recovery complete: " + recoverable); } } }; private ExecutorService executorService; private List<Address> addresses; private boolean shuffleAddresses; private int closeTimeout = DEFAULT_CLOSE_TIMEOUT; private ConnectionNameStrategy connectionNameStrategy = connectionFactory -> (this.beanName != null ? this.beanName : "SpringAMQP") + "#" + ObjectUtils.getIdentityHexString(this) + ":" + this.defaultConnectionNameStrategyCounter.getAndIncrement(); private String beanName; private ApplicationContext applicationContext; private ApplicationEventPublisher applicationEventPublisher; private volatile boolean contextStopped; /** * Create a new AbstractConnectionFactory for the given target ConnectionFactory, * with no publisher connection factory. * @param rabbitConnectionFactory the target ConnectionFactory */ public AbstractConnectionFactory(com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory) { Assert.notNull(rabbitConnectionFactory, "Target ConnectionFactory must not be null"); this.rabbitConnectionFactory = rabbitConnectionFactory; } protected final void setPublisherConnectionFactory(AbstractConnectionFactory publisherConnectionFactory) { this.publisherConnectionFactory = publisherConnectionFactory; } @Override public void setApplicationContext(ApplicationContext applicationContext) { this.applicationContext = applicationContext; if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setApplicationContext(applicationContext); } } protected ApplicationContext getApplicationContext() { return this.applicationContext; } @Override public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { this.applicationEventPublisher = applicationEventPublisher; if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setApplicationEventPublisher(applicationEventPublisher); } } protected ApplicationEventPublisher getApplicationEventPublisher() { return this.applicationEventPublisher; } @Override public void onApplicationEvent(ContextClosedEvent event) { if (getApplicationContext() == event.getApplicationContext()) { this.contextStopped = true; } if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.onApplicationEvent(event); } } protected boolean getContextStopped() { return this.contextStopped; } /** * Return a reference to the underlying Rabbit Connection factory. * @return the connection factory. * @since 1.5.6 */ public com.rabbitmq.client.ConnectionFactory getRabbitConnectionFactory() { return this.rabbitConnectionFactory; } /** * Return the user name from the underlying rabbit connection factory. * @return the user name. * @since 1.6 */ @Override public String getUsername() { return this.rabbitConnectionFactory.getUsername(); } public void setUsername(String username) { this.rabbitConnectionFactory.setUsername(username); } public void setPassword(String password) { this.rabbitConnectionFactory.setPassword(password); } public void setHost(String host) { this.rabbitConnectionFactory.setHost(host); } /** * Set the {@link ThreadFactory} on the underlying rabbit connection factory. * @param threadFactory the thread factory. * @since 1.5.3 */ public void setConnectionThreadFactory(ThreadFactory threadFactory) { this.rabbitConnectionFactory.setThreadFactory(threadFactory); } /** * @param uri the URI * @since 1.5 * @see com.rabbitmq.client.ConnectionFactory#setUri(URI) */ public void setUri(URI uri) { try { this.rabbitConnectionFactory.setUri(uri); } catch (URISyntaxException | GeneralSecurityException use) { this.logger.info(BAD_URI, use); } } /** * @param uri the URI * @since 1.5 * @see com.rabbitmq.client.ConnectionFactory#setUri(String) */ public void setUri(String uri) { try { this.rabbitConnectionFactory.setUri(uri); } catch (URISyntaxException | GeneralSecurityException use) { this.logger.info(BAD_URI, use); } } @Override public String getHost() { return this.rabbitConnectionFactory.getHost(); } public void setVirtualHost(String virtualHost) { this.rabbitConnectionFactory.setVirtualHost(virtualHost); } @Override public String getVirtualHost() { return this.rabbitConnectionFactory.getVirtualHost(); } public void setPort(int port) { this.rabbitConnectionFactory.setPort(port); } public void setRequestedHeartBeat(int requestedHeartBeat) { this.rabbitConnectionFactory.setRequestedHeartbeat(requestedHeartBeat); } public void setConnectionTimeout(int connectionTimeout) { this.rabbitConnectionFactory.setConnectionTimeout(connectionTimeout); } @Override public int getPort() { return this.rabbitConnectionFactory.getPort(); } /** * Set addresses for clustering. * This property overrides the host+port properties if not empty. * @param addresses list of addresses with form "host[:port],..." */ public void setAddresses(String addresses) { if (StringUtils.hasText(addresses)) { Address[] addressArray = Address.parseAddresses(addresses); if (addressArray.length > 0) { this.addresses = Arrays.asList(addressArray); if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setAddresses(addresses); } return; } } this.logger.info( "setAddresses() called with an empty value, will be using the host+port properties for connections"); this.addresses = null; } /** * A composite connection listener to be used by subclasses when creating and closing connections. * * @return the connection listener */ protected ConnectionListener getConnectionListener() { return this.connectionListener; } /** * A composite channel listener to be used by subclasses when creating and closing channels. * * @return the channel listener */ protected ChannelListener getChannelListener() { return this.channelListener; } public void setConnectionListeners(List<? extends ConnectionListener> listeners) { this.connectionListener.setDelegates(listeners); if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setConnectionListeners(listeners); } } @Override public void addConnectionListener(ConnectionListener listener) { this.connectionListener.addDelegate(listener); if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.addConnectionListener(listener); } } @Override public boolean removeConnectionListener(ConnectionListener listener) { boolean result = this.connectionListener.removeDelegate(listener); if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.removeConnectionListener(listener); // NOSONAR } return result; } @Override public void clearConnectionListeners() { this.connectionListener.clearDelegates(); if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.clearConnectionListeners(); } } public void setChannelListeners(List<? extends ChannelListener> listeners) { this.channelListener.setDelegates(listeners); } /** * Set a {@link RecoveryListener} that will be added to each connection created. * @param recoveryListener the listener. * @since 2.0 */ public void setRecoveryListener(RecoveryListener recoveryListener) { this.recoveryListener = recoveryListener; if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setRecoveryListener(recoveryListener); } } public void addChannelListener(ChannelListener listener) { this.channelListener.addDelegate(listener); if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.addChannelListener(listener); } } /** * Provide an Executor for * use by the Rabbit ConnectionFactory when creating connections. * Can either be an ExecutorService or a Spring * ThreadPoolTaskExecutor, as defined by a <task:executor/> element. * @param executor The executor. */ public void setExecutor(Executor executor) { boolean isExecutorService = executor instanceof ExecutorService; boolean isThreadPoolTaskExecutor = executor instanceof ThreadPoolTaskExecutor; Assert.isTrue(isExecutorService || isThreadPoolTaskExecutor, "'executor' must be an 'ExecutorService' or a 'ThreadPoolTaskExecutor'"); if (isExecutorService) { this.executorService = (ExecutorService) executor; } else { this.executorService = ((ThreadPoolTaskExecutor) executor).getThreadPoolExecutor(); } if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setExecutor(executor); } } @Nullable protected ExecutorService getExecutorService() { return this.executorService; } /** * How long to wait (milliseconds) for a response to a connection close * operation from the broker; default 30000 (30 seconds). * @param closeTimeout the closeTimeout to set. */ public void setCloseTimeout(int closeTimeout) { this.closeTimeout = closeTimeout; if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setCloseTimeout(closeTimeout); } } public int getCloseTimeout() { return this.closeTimeout; } /** * Provide a {@link ConnectionNameStrategy} to build the name for the target RabbitMQ connection. * The {@link #beanName} together with a counter is used by default. * @param connectionNameStrategy the {@link ConnectionNameStrategy} to use. * @since 2.0 */ public void setConnectionNameStrategy(ConnectionNameStrategy connectionNameStrategy) { this.connectionNameStrategy = connectionNameStrategy; if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setConnectionNameStrategy( cf -> connectionNameStrategy.obtainNewConnectionName(cf) + PUBLISHER_SUFFIX); } } @Override public void setBeanName(String name) { this.beanName = name; if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.setBeanName(name + PUBLISHER_SUFFIX); } } /** * Return a bean name of the component or null if not a bean. * @return the bean name or null. * @since 1.7.9 */ @Nullable protected String getBeanName() { return this.beanName; } /** * When {@link #setAddresses(String) addresses} are provided and there is more than * one, set to true to shuffle the list before opening a new connection so that the * connection to the broker will be attempted in random order. * @param shuffleAddresses true to shuffle the list. * @since 2.1.8 * @see Collections#shuffle(List) */ public void setShuffleAddresses(boolean shuffleAddresses) { this.shuffleAddresses = shuffleAddresses; } public boolean hasPublisherConnectionFactory() { return this.publisherConnectionFactory != null; } @Override public ConnectionFactory getPublisherConnectionFactory() { return this.publisherConnectionFactory; } protected final Connection createBareConnection() { try { String connectionName = this.connectionNameStrategy.obtainNewConnectionName(this); com.rabbitmq.client.Connection rabbitConnection = connect(connectionName); Connection connection = new SimpleConnection(rabbitConnection, this.closeTimeout); if (rabbitConnection instanceof AutorecoveringConnection) { ((AutorecoveringConnection) rabbitConnection).addRecoveryListener(new RecoveryListener() { @Override public void handleRecoveryStarted(Recoverable recoverable) { handleRecovery(recoverable); } @Override public void handleRecovery(Recoverable recoverable) { try { connection.close(); } catch (Exception e) { AbstractConnectionFactory.this.logger.error("Failed to close auto-recover connection", e); } } }); } if (this.logger.isInfoEnabled()) { this.logger.info("Created new connection: " + connectionName + "/" + connection); } if (this.recoveryListener != null && rabbitConnection instanceof AutorecoveringConnection) { ((AutorecoveringConnection) rabbitConnection).addRecoveryListener(this.recoveryListener); } if (this.applicationEventPublisher != null) { connection.addBlockedListener( new ConnectionBlockedListener(connection, this.applicationEventPublisher)); } return connection; } catch (IOException | TimeoutException e) { throw RabbitExceptionTranslator.convertRabbitAccessException(e); } } private com.rabbitmq.client.Connection connect(String connectionName) throws IOException, TimeoutException { com.rabbitmq.client.Connection rabbitConnection; if (this.addresses != null) { List<Address> addressesToConnect = this.addresses; if (this.shuffleAddresses && addressesToConnect.size() > 1) { List<Address> list = new ArrayList<>(addressesToConnect); Collections.shuffle(list); addressesToConnect = list; } if (this.logger.isInfoEnabled()) { this.logger.info("Attempting to connect to: " + addressesToConnect); } rabbitConnection = this.rabbitConnectionFactory.newConnection(this.executorService, addressesToConnect, connectionName); } else { if (this.logger.isInfoEnabled()) { this.logger.info("Attempting to connect to: " + this.rabbitConnectionFactory.getHost() + ":" + this.rabbitConnectionFactory.getPort()); } rabbitConnection = this.rabbitConnectionFactory.newConnection(this.executorService, connectionName); } return rabbitConnection; } protected final String getDefaultHostName() { String temp; try { InetAddress localMachine = InetAddress.getLocalHost(); temp = localMachine.getHostName(); this.logger.debug("Using hostname [" + temp + "] for hostname."); } catch (UnknownHostException e) { this.logger.warn("Could not get host name, using 'localhost' as default value", e); temp = "localhost"; } return temp; } @Override public void destroy() { if (this.publisherConnectionFactory != null) { this.publisherConnectionFactory.destroy(); } } @Override public String toString() { if (this.beanName != null) { return this.beanName; } else { return super.toString(); } } private static final class ConnectionBlockedListener implements BlockedListener { private final Connection connection; private final ApplicationEventPublisher applicationEventPublisher; ConnectionBlockedListener(Connection connection, ApplicationEventPublisher applicationEventPublisher) { this.connection = connection; this.applicationEventPublisher = applicationEventPublisher; } @Override public void handleBlocked(String reason) { this.applicationEventPublisher.publishEvent(new ConnectionBlockedEvent(this.connection, reason)); } @Override public void handleUnblocked() { this.applicationEventPublisher.publishEvent(new ConnectionUnblockedEvent(this.connection)); } } }