本文摘自https://www.jianshu.com/p/d41f32ca22a5 如有问题,请在文章评论处留言
背景
ActiveMQ是非常受欢迎且功能强大的开源消息中间件。它具有速度快、支持多种语言和协议、附带易于使用的企业集成模式和诸多的高级功能,同时完全支持JMS1.1和J2EE1.4规范。在诸多系统系统中都有广泛的应用。并且社区活跃、文档丰富,是系统异步交互的不二选择。
作为一个消息中间件,有客户端和服务端两部分代码,本次主要针对客户端进行源码分析,分为建立连接、消息发送、消息消费三个部分。
文章分析的源码版本基于Activemq-5.15.4
版本。历史版本大家可以去github上查看。
建立连接
通常来说,客户端使用MQ的API建立连接时,可以分为两个步骤:
- 连接的配置,比如服务器IP地址、用户名和密码等
- 建立连接并启动
客户端示例代码:
ActiveMQConnectionFactory connectionFactory = new ActiveMQConnectionFactory(username,password,url);
ActiveMQConnection connection = connectionFactory.createConnection();
connection.start();
可以看到主要的方法是ActiveMQConnectionFactory构造函数、createConnection()函数以及connection的start()函数。
配置
ActiveMQConnectionFactory中的createConnection
首先我们来看一下构造连接工厂都做了什么。
public ActiveMQConnectionFactory() {
this(DEFAULT_BROKER_URL);
}
public ActiveMQConnectionFactory(String brokerURL) {
this(createURI(brokerURL));
}
public ActiveMQConnectionFactory(URI brokerURL) {
setBrokerURL(brokerURL.toString());
}
public ActiveMQConnectionFactory(String userName, String password, URI brokerURL) {
setUserName(userName);
setPassword(password);
setBrokerURL(brokerURL.toString());
}
主要的构造器有四个,基本上就是设置用户名密码(非必须)和URI对象。
创建连接
它的createConnection方法指向了createActiveMQConnection方法,主要做了三件事情:
- 建立Transport并通过其建立Connection
- 配置Connection,建立好的Transport对象被放入其中
- 启动Transport
/**
* @return Returns the Connection.
*/
protected ActiveMQConnection createActiveMQConnection(String userName, String password) throws JMSException {
if (brokerURL == null) {
throw new ConfigurationException("brokerURL not set.");
}
ActiveMQConnection connection = null;
try {
Transport transport = createTransport();
connection = createActiveMQConnection(transport, factoryStats);
connection.setUserName(userName);
connection.setPassword(password);
configureConnection(connection);
transport.start();
if (clientID != null) {
connection.setDefaultClientID(clientID);
}
return connection;
} catch (JMSException e) {
// Clean up!
try {
connection.close();
} catch (Throwable ignore) {
}
throw e;
} catch (Exception e) {
// Clean up!
try {
connection.close();
} catch (Throwable ignore) {
}
throw JMSExceptionSupport.create("Could not connect to broker URL: " + brokerURL + ". Reason: " + e, e);
}
}
我们来详细看一下连接是如何创立的。 createTransport
方法中包含了对客户端传入的url的初步校验,主要是验证URL的合法性,而后调用工厂类TransportFactory.connection(url)
来进行连接的建立。
/**
* Creates a Transport based on this object's connection settings. Separated
* from createActiveMQConnection to allow for subclasses to override.
*
* @return The newly created Transport.
* @throws JMSException If unable to create trasnport.
*/
protected Transport createTransport() throws JMSException {
try {
URI connectBrokerUL = brokerURL;
String scheme = brokerURL.getScheme();
if (scheme == null) {
throw new IOException("Transport not scheme specified: [" + brokerURL + "]");
}
if (scheme.equals("auto")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto", "tcp"));
} else if (scheme.equals("auto+ssl")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto+ssl", "ssl"));
} else if (scheme.equals("auto+nio")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto+nio", "nio"));
} else if (scheme.equals("auto+nio+ssl")) {
connectBrokerUL = new URI(brokerURL.toString().replace("auto+nio+ssl", "nio+ssl"));
}
return TransportFactory.connect(connectBrokerUL);
} catch (Exception e) {
throw JMSExceptionSupport.create("Could not create Transport. Reason: " + e, e);
}
}
我们客户端在建立连接的时候,有可能有TCP、UDP等等协议,AMQ实现了简单工厂类FactoryFinder
,在TransportFactory.connect(connectBrokerUL)
方法中,先是通过FactoryFinder
根据用户输入的url(比如tcp://192.168.0.1)来找到使用的协议工厂TcpTransportFactory
,然后使用TcpTransportFactory
中的类来进行连接的建立。这个过程从代码上来看有点曲折:
- TransportFactory的connect()调用findTransportFactory方法
- findTransportFactory调用FactoryFinder类的newInstance方法
- newInstance调用ObjectFactory类的create方法
- ObejctFactory是一个接口类,实现类是StandaloneObjectFactory,其中的create方法调用自身的loadClass方法
- loadClass方法中最终找到正确的类,返回至TransportFactory中
- 如果是tcp连接,最终得到的就是一个实例化的TcpTransportFactory类
public abstact class TransportFactory {
……
private static final FactoryFinder TRANSPORT_FACTORY_FINDER = new FactoryFinder("META-INF/services/org/apache/activemq/transport/");
public static Transport connect(URI location) throws Exception {
TransportFactory tf = findTransportFactory(location);
return tf.doConnect(location);
}
public static TransportFactory findTransportFactory(URI location) throws IOException {
String scheme = location.getScheme();
if (scheme == null) {
throw new IOException("Trans