SLF4J 英文全称是 Simple Logging Facade for Java, 是一个门面(外观)接口或者说各种日志框架的抽象,比如 java.util.logging, logback, log4j 等;使用这货,我们可以不用关心具体的实现,也就是说可以随时切换日志框架。
这边使用的是目前最新版本的 slf4j 1.8.0-beta2
简单使用下试试
示例代码 https://github.com/minorpoet/logging-slf4j
- 添加依赖
dependencies {
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.8.0-beta2'
}
- 使用
package pri.holysu.logging.sl4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class HelloWorld {
public static void main(String[] args) {
Logger logger = LoggerFactory.getLogger(HelloWorld.class);
logger.info("hello world");
}
}
-
运行
发现报错了,提示我们没找到 slf4j 的实现
咋办? 加一个
在 build.gradle 依赖中增加一个 logback
dependencies {
compile group: 'org.slf4j', name: 'slf4j-api', version: '1.8.0-beta2'
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.3.0-alpha4'
}
然后再运行,发现可以了
但是,我没有做任何设置,怎么就能选取 logback 的日志框架呢?
我们看看使用方式, Logger logger = LoggerFactory.getLogger(HelloWorld.class);
这个日志工厂的静态方法 org.slf4j.LoggerFactory.getLogger
public static Logger getLogger(Class<?> clazz) {
// 通过类名获取 Logger
Logger logger = getLogger(clazz.getName());
// 如果配置文件中设置开启“检查日志名称匹配” 的话,则在匹配失败时,报告错误
if (DETECT_LOGGER_NAME_MISMATCH) {
Class<?> autoComputedCallingClass = Util.getCallingClass();
if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
autoComputedCallingClass.getName()));
Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
}
}
return logger;
}
再看看 getLogger
public static Logger getLogger(String name) {
// 获取日志工厂
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
// 从日志框架提供者(实现),中获取日志工厂
return getProvider().getLoggerFactory();
}
找到这里, getProvider
这个方法,按照字面意思应该就是我们要找的地方了
static SLF4JServiceProvider getProvider() {
// 当状态为未初始化时,这边是 double-checked-lock 方式
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
// 状态置为,初始化ing
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
// 执行初始化逻辑
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return PROVIDER;
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
case FAILED_INITIALIZATION:
throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
case ONGOING_INITIALIZATION:
// support re-entrant behavior.
// See also http://jira.qos.ch/browse/SLF4J-97
return SUBST_PROVIDER;
}
throw new IllegalStateException("Unreachable code");
}
具体的初始化过程:
private final static void performInitialization() {
// 绑定日志框架,这边就是核心了
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
// 初始化完成后,检查日志框架是否健康
versionSanityCheck();
}
}
private final static void bind() {
try {
// 加载 slf4j 的实现方
List<SLF4JServiceProvider> providersList = findServiceProviders();
reportMultipleBindingAmbiguity(providersList);
if (providersList != null && !providersList.isEmpty()) {
// 只获取第一个实现
PROVIDER = providersList.get(0);
PROVIDER.initialize();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(providersList);
fixSubstituteLoggers();
replayEvents();
// release all resources in SUBST_FACTORY
SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
} else {
// 一开始未引入 logback 包的时候,报的就是这个错
INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
Util.report("No SLF4J providers were found.");
Util.report("Defaulting to no-operation (NOP) logger implementation");
Util.report("See " + NO_PROVIDERS_URL + " for further details.");
Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
}
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
findServiceProviders
的具体逻辑就很清晰了,通过类加载器加载指定类型
private static List<SLF4JServiceProvider> findServiceProviders() {
// 通过 ServiceLoader 这个接口加载工具类,加载类型为 SLF4JServiceProvider 的类
ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
// 加入到列表中, 通过上面bind() 中 PROVIDER = providersList.get(0); 可知,即使有多个,也只会使用第一个加载进来的实现类
List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
for (SLF4JServiceProvider provider : serviceLoader) {
providerList.add(provider);
}
return providerList;
}
// 通过当前线程的类加载器,加载类型为 service 的类
public static <S> ServiceLoader<S> load(Class<S> service) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
return ServiceLoader.load(service, cl);
}
org.slf4j.LoggerFactory.getLogger
免去了诸如 ILogger logger = new LoggerImp();
或者 bean 声明等的繁琐,只要实现存在于 classpath 中就可以了,我们需要记录日志的时候只需要一种格式就可以了,而不用理会各种日志框架的实现差异, 这估计就是规范的一种魅力~
如果你用了 lombok,那么会更简洁,如
package pri.holysu.logging.sl4j;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class LombokSlf4j {
public static void main(String[] args) {
log.info("logger feteched from lombok");
}
}
其中 log 是 lombok 给自动添加进来的, 是不是很方便?