目录
写在最前面
简单地说,在使用slf4j+具体实现的时候,只需要将slf4j-api和对应的桥接jar包放到class路径里面即可。多数项目以maven项目管理包依赖的,例如:
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
上面配置代表自动使用log4j日志实现类,因此还需要在加上log4j的依赖
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
以上配置即可开始编程开发,使用org.slf4j.LoggerFactory.getLogger获取到Logger即可,slf4j会自动找到Log4j实现进行工作。下面会进行讲解。
初学java的同学比较容易对上述几种日志相关的库搞混淆,不清楚几者的关系。虽然自己已经从事JAVA开发很久了,实际上也没有彻底弄清楚slf4j是怎么寻找实际日志实现类进行日志打印的。今天心血来潮,看了一下源代码,记录一下。
slf4j是一个门面库
SLF4J,英文原文为Simple Logging Facade for Java,简单日志门面。Facade即Facade模式(也叫做外观模式)的意思,外观模式,一般指一个系统与外部系统进行通信、交互时,所有的动作、命令都通过一个叫做Facade的进行。
实际上JAVA开发者平时提出的面向接口编程也是一种类似的概念。Facade和接口提供一种通信、交互的契约和规范,达到外部系统和实现类、内部系统解耦。slf4j-api即是这样一个类库,这个库中约定了一个日志库应该提供的方法列表,在org.slf4j.Logger接口描述中。外部系统按照这个接口约定进行日志的记录,至于具体使用的是什么日志实现,应用程序无需知道。
slf4j+具体实现的合作关系
这种组合方式要工作,必须需要配置slf4j-api,slf4j-jdk14/slf4j-log4j12/slf4j-nop, 具体log实现库,缺一不可。为什么三个jar包放到一起就可以工作,无需在代码层次在做其他工作。
将三者紧密连接起来的是存在于每一个桥接包中的StaticLoggerBinder类(存在于各个桥接包中),该类是LoggerFactoryBinder接口(存在于slf4j-api)的实现类。因此桥接包值能加载一个。
slf4j+log4j的组合
以slf4j-api+slf4j-log4j12+log4j为例,讲解怎么通过slf4j-api调用到log4j的。
LoggerFactory.getLogger方法具体经过了一下路径:
- LoggerFactory类import StaticLoggerBinder类,该StaticLoggerBinder并不在slf4j-api包实现,存在于各个桥接包中。
- 应用程序在类路径下面slf4j-log4j12包中找到StaticLoggerBinder类的实现
- StaticLoggerBinder实现为单例模式,此类提供了一个log4j的适配类Log4jLoggerFactory,该适配类又会去寻找log4j的实现代码,然后进行包装起来。
- 最后通过getLogger得到的logger实际上就是Log4jLoggerFactory实现代码:
public Logger getLogger(String name) { Logger slf4jLogger = null; // protect against concurrent access of loggerMap synchronized (this) { slf4jLogger = (Logger) loggerMap.get(name); if (slf4jLogger == null) { org.apache.log4j.Logger log4jLogger; if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) { log4jLogger = LogManager.getRootLogger(); } else { log4jLogger = LogManager.getLogger(name); } slf4jLogger = new Log4jLoggerAdapter(log4jLogger); loggerMap.put(name, slf4jLogger); } } return slf4jLogger; }
代码中直接引用了org.apache.log4j.Logger类,因此三者边联系起来了。
作为对比,我们再讲解slf4j+noploger
slf4j+noploger
NOPLogger是slf4j提供的一个默认日志实现(实际上是一个空实现,里面没有做任何日志记录工作,单纯的只是接受日志记录请求并且抛弃)。
- LoggerFactory类import StaticLoggerBinder类,该StaticLoggerBinder存在于slf4j-nop包中。
- StaticLoggerBinder提供的日志适配器是NOPLoggerFactory类,该类存在于slf4j-api包中
NOPLoggerFactory提供的实现类代码比较短,很直观,他本身在调用getLogger时返回的NOPLogger.NOP_LOGGER类。
NOPLoggerFactory代码如下
public class NOPLoggerFactory implements ILoggerFactory {
public NOPLoggerFactory() {
// nothing to do
}
public Logger getLogger(String name) {
return NOPLogger.NOP_LOGGER;
}
}
再来看看NOP_LOGGER实现
public class NOPLogger extends MarkerIgnoringBase {
private static final long serialVersionUID = -517220405410904473L;
/**
* The unique instance of NOPLogger.
*/
public static final NOPLogger NOP_LOGGER = new NOPLogger();
/**
* There is no point in creating multiple instances of NOPLOgger,
* except by derived classes, hence the protected access for the constructor.
*/
protected NOPLogger() {
}
/**
* Always returns the string value "NOP".
*/
public String getName() {
return "NOP";
}
/**
* Always returns false.
* @return always false
*/
final public boolean isTraceEnabled() {
return false;
}
/** A NOP implementation. */
final public void trace(String msg) {
// NOP
}
/** A NOP implementation. */
final public void trace(String format, Object arg) {
// NOP
}
/** A NOP implementation. */
public final void trace(String format, Object arg1, Object arg2) {
// NOP
}
// ...省略
}
此类可以比较直观的看到slf的工作模式。