logMessageListener指向我们自己实现的日志消息处理逻辑类,topicDestination则关注topic为“logTopic”的消息,而jmsContainer把这两个对象绑在一起,这样就能接收并处理消息了。
最后就是伟大的监听器了LogMessageListener了:
package com.demo.logging;
import javax.jms.Message;
import javax.jms.MessageListener;
import org.apache.activemq.command.ActiveMQObjectMessage;
import org.apache.log4j.spi.LoggingEvent;
public class LogMessageListener implements MessageListener {
public void onMessage(Message message) {
try {
// receive log event in your consumer
LoggingEvent event = (LoggingEvent)((ActiveMQObjectMessage)message).getObject();
System.out.println("Logging project: [" + event.getLevel() + "]: "+ event.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
}
哈哈,说伟大,其实太简单了。但是可以看到,监听器里面就是之前Product项目中Main类里面移除的实现了MessageListener接口中的代码。
测试
在执行测试前,删掉ActiveMQ中所有的Queue,确保测试效果。
先运行Logging项目,开始Queue的监听。再运行Product的Main类的main函数,可以先看到Main类打印到控制台的日志:
接下来去看看Queue中的情况:
可以看到有个叫logTopic的主题的消息,进了3条,出了3条。不用想,出Queue的3条日志已经被Logging项目的Listener接收并打印出来了,现在去看看Tomcat的控制台:
还要注意Queue中的logTopic的Consumer数量为1而不是0,这与开始的截图不同。我们都知道这个Consumer是Logging项目中的LogMessageListener对象,它一直活着,是因为Tomcat一直活着;之前的Consumer数量为0,是因为在main函数执行完后,Queue的监听器(也是写日志的对象)就退出了。
通过把Product和Logging项目分别放在不同的机器上执行,在第三台机器上部署ActiveMQ(当然你可以把ActiveMQ搭建在任意可以访问的地方),再配置一下Product项目的log4j.properties文件和Logging项目的spring.xml文件就能用于生产环境啦。
JMSAppender类的分析
JMSAppender类将LoggingEvent实例序列化成ObjectMessage,并将其发送到JMS Server的一个指定Topic中,因此,使用此种将日志发送到远程的方式只支持Topic方式发送,不支持Queue方式发送。我们再log4j.properties中配置了这一句:
log4j.appender.jms=org.apache.log4j.net.JMSAppender
这一句指定了使用的Appender,打开这个Appender,在里面可以看到很多setter,比如:
这些setter不是巧合,而正是对应了我们在log4j.properties中设置的其他几个选项:
log4j.appender.jms.InitialContextFactoryName=org.apache.activemq.jndi.ActiveMQInitialContextFactory
log4j.appender.jms.ProviderURL=tcp://localhost:61616
log4j.appender.jms.TopicBindingName=logTopic
log4j.appender.jms.TopicConnectionFactoryBindingName=ConnectionFactory
来看看JMSAppender的activeOptions方法,这个方法是用于使我们在log4j.properties中的配置生效的:
/**
* Options are activated and become effective only after calling this method.
*/
public void activateOptions() {
TopicConnectionFactory topicConnectionFactory;
try {
Context jndi;
LogLog.debug("Getting initial context.");
if (initialContextFactoryName != null) {
Properties env = new Properties();
env.put(Context.INITIAL_CONTEXT_FACTORY, initialContextFactoryName);
if (providerURL != null) {
env.put(Context.PROVIDER_URL, providerURL);
} else {
LogLog.warn("You have set InitialContextFactoryName option but not the "
+ "ProviderURL. This is likely to cause problems.");
}
if (urlPkgPrefixes != null) {
env.put(Context.URL_PKG_PREFIXES, urlPkgPrefixes);
}
if (securityPrincipalName != null) {
env.put(Context.SECURITY_PRINCIPAL, securityPrincipalName);
if (securityCredentials != null) {
env.put(Context.SECURITY_CREDENTIALS, securityCredentials);
} else {
LogLog.warn("You have set SecurityPrincipalName option but not the "
+ "SecurityCredentials. This is likely to cause problems.");
}
}
jndi = new InitialContext(env);
} else {
jndi = new InitialContext();
}
LogLog.debug("Looking up [" + tcfBindingName + "]");
topicConnectionFactory = (TopicConnectionFactory) lookup(jndi, tcfBindingName);
LogLog.debug("About to create TopicConnection.");
///////////////////////////////注意这里只会创建TopicConnection////////////////////////////
if (userName != null) {
topicConnection = topicConnectionFactory.createTopicConnection(userName, password);
} else {
topicConnection = topicConnectionFactory.createTopicConnection();
}
LogLog.debug("Creating TopicSession, non-transactional, " + "in AUTO_ACKNOWLEDGE mode.");
topicSession = topicConnection.createTopicSession(false, Session.AUTO_ACKNOWLEDGE);
LogLog.debug("Looking up topic name [" + topicBindingName + "].");
Topic topic = (Topic) lookup(jndi, topicBindingName);
LogLog.debug("Creating TopicPublisher.");
topicPublisher = topicSession.createPublisher(topic);
LogLog.debug("Starting TopicConnection.");
topicConnection.start();
jndi.close();
} catch (JMSException e) {
errorHandler.error("Error while activating options for appender named [" + name + "].", e,
ErrorCode.GENERIC_FAILURE);
} catch (NamingException e) {
errorHandler.error("Error while activating options for appender named [" + name + "].", e,
ErrorCode.GENERIC_FAILURE);
} catch (RuntimeException e) {
errorHandler.error("Error while activating options for appender named [" + name + "].", e,
ErrorCode.GENERIC_FAILURE);
}
}
上面初始化了一个TopicConnection,一个TopicSession,一个TopicPublisher。咱们再来看看这个Appender的append方法: