背景
工程中使用的spring4.2.6,在web.xml配置了logback的listener加载logback配置文件,使用logback打印日志,但是启动后spring core模块使用jcl(jarakta commons logging)打印,后边的使用logback打印。
分析
通过maven dependency发现,工程引入了jcl(jarakta commons logging)、log4j、slf4j、logback等日志包。查阅spring官方文档 得知,spring core模块使用的jcl日志,这也就解释了,为什么前半部分用jcl打印,所以,需要将其他日志实现引入到slf4j。为了达到这个目的,需要做以下措施
修正
- 将commons-logging包全部移除,并引入jcl-over-slf4j.jar,这个包有两个作用,第一,防止出现jcl类的classnotfoundexception;第二,并将此类log引入slf4j。
- 将log4j包全部移除,并引入log4j-over-slf4j.jar,作用同上,防止使用log4j的程序出现clasnotfoundexception,并将此类log引入slf4j
- 将web.xml中logbacklistener放在所有listener的前面,首先执行logbackconfiglistener然后再执行spring的listener防止,这一点牵扯到了listener—–>filter—–>servlet的初始化顺序
所以最终的配置就是
jcl-over-slf4j.jar
logback-classic.jar
logback-core.jar
做完以上后,log恢复正常了。
收获
这个问题,解答了我之前的很多疑惑:系统中存在多个日志实现,比如jcl、log4j,第一,怎么将所有日志统一到某一个实现?第二,如果移除这些jar包,又会出现ClassNotFoundException,又该怎么解决?答案很简单,桥接!拿jcl举例,引入的jcl-over-slf4j.jar,就是将日志引入到slf4j,里边含有jcl的类,所以不会出现ClassNotFoundException,打开jcl-over-slf4j.jar与commons-logging.jar,你会发现类都一样。
桥接模式在jcl的实现
桥接模式很简单,就是A类引用了另一个类B,A类的方法最终都走的B类的方法。
这里jcl-over-slf4j,实现原理是桥接模式,看jcl-over-slf4j.jar的类LoggerFactory中包含了slf4j的LoggerFactory,这是第一个桥接模式的应用;
public static LogFactory getFactory() throws LogConfigurationException {
return logFactory;
}
static LogFactory logFactory = new SLF4JLogFactory();
返回的jcl logger类中,引用了slf4j的logger,这是第二个桥接模式的应用;
public Log getInstance(String name) throws LogConfigurationException {
Log instance = loggerMap.get(name);
if (instance != null) {
return instance;
} else {
Log newInstance;
Logger slf4jLogger = LoggerFactory.getLogger(name);
if (slf4jLogger instanceof LocationAwareLogger) {
newInstance = new SLF4JLocationAwareLog((LocationAwareLogger) slf4jLogger);
} else {
newInstance = new SLF4JLog(slf4jLogger);
}
Log oldInstance = loggerMap.putIfAbsent(name, newInstance);
return oldInstance == null ? newInstance : oldInstance;
}
}
这里看SLF4J的实现,日志最终都走的logger,而logger就是上边传入的slf4jLogger,slf4jLogger来自SLF4JFactory
public class SLF4JLog implements Log, Serializable {
private static final long serialVersionUID = 680728617011167209L;
// used to store this logger's name to recreate it after serialization
protected String name;
// in both Log4jLogger and Jdk14Logger classes in the original JCL, the
// logger instance is transient
private transient Logger logger;
SLF4JLog(Logger logger) {
this.logger = logger;
this.name = logger.getName();
}
}
正是这两个桥接模式的应用才使的jcl的log引入到slf4j中
我们再联想,slf4j-xx.jar(),很多这种jar,也是这个道理,桥接模式的应用;