业务中经常用到slf4j来写日志,但是没有深入研究过为啥通过这个就可以调用log4j或者logback的函数来写日志呢?
一、优势
《阿里巴巴Java开发手册》关于日志章节专门提到:
【强制】应用中不可直接使用日志系统(Log4j、Logback)中的 API,而应依赖使用日志框架 SLF4J 中的 API,使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一。 import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);
今天觉得有必要研究一下。
二、原理
slf4j采用门面模式,即把自己作为一个日志接口,并不提供实现。
这里调用log4j或者Logback的实现。我的演示代码用的是logback。
代码语言:javascript复制 LoggerFactory.getLogger(xxx.class)
追踪LoggerFactory.getLogger源码
代码语言:javascript复制 public static Logger getLogger(Class clazz) {
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;
}
调用是如下方法(直接获取类名当做logger名称传入):
代码语言:javascript复制 /**
* Return a logger named according to the name parameter using the
* statically bound {@link ILoggerFactory} instance.
*
* @param name
* The name of the logger.
* @return logger
*/
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
我们看下getILoggerFacotry函数
代码语言:javascript复制/**
* Return the {@link ILoggerFactory} instance in use.
*
*
* ILoggerFactory instance is bound with this class at compile time.
*
* @return the ILoggerFactory instance in use
*/
public static ILoggerFactory getILoggerFactory() {
// 这里用了double-check!!!
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
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_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
我们发现关键的代码在这里:
代码语言:javascript复制 StaticLoggerBinder.getSingleton().getLoggerFactory()
我们跟进去,发现这个类包名为org.slf4j.impl,但是在logback包里!!!!!!!!
我们发现该类使用了单例模式
getLoggerFactory最终返回LoggerContext类
代码语言:javascript复制 public ILoggerFactory getLoggerFactory() {
if (!initialized) {
return defaultLoggerContext;
}
if (contextSelectorBinder.getContextSelector() == null) {
throw new IllegalStateException("contextSelector cannot be null. See also " NULL_CS_URL);
}
return contextSelectorBinder.getContextSelector().getLoggerContext();
}
获取ContextSelector然后获取LoggerContext。
代码语言:javascript复制public interface ContextSelector {
// 嗨,看这里!!
LoggerContext getLoggerContext();
LoggerContext getLoggerContext(String name);
LoggerContext getDefaultLoggerContext();
LoggerContext detachLoggerContext(String loggerContextName);
List getContextNames();
}
最终调用了LoggerContext的getLogger方法
源码如下:
代码语言:javascript复制 public final Logger getLogger(final Class clazz) {
return getLogger(clazz.getName());
}
@Override
public final Logger getLogger(final String name) {
if (name == null) {
throw new IllegalArgumentException("name argument cannot be null");
}
// 如果要的是ROOT logger直接返回
if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) {
return root;
}
int i = 0;
Logger logger = root;
// 检查是否存在,如果存在直接返回(从缓存中拿)
Logger childLogger = (Logger) loggerCache.get(name);
if (childLogger != null) {
return childLogger;
}
// 如果想要的logger不存在, 创建所需的logger,也包括中间的logger
String childName;
while (true) {
int h = LoggerNameUtil.getSeparatorIndexOf(name, i);
if (h == -1) {
childName = name;
} else {
childName = name.substring(0, h);
}
// move i left of the last point
i = h 1;
synchronized (logger) {
childLogger = logger.getChildByName(childName);
if (childLogger == null) {
childLogger = logger.createChildByName(childName);
// 将Logger放到缓存中
loggerCache.put(childName, childLogger);
incSize();
}
}
logger = childLogger;
if (h == -1) {
return childLogger;
}
}
}
这个类中还包括获取所有Logger的函数
代码语言:javascript复制 public List getLoggerList() {
Collection collection = loggerCache.values();
List loggerList = new ArrayList(collection);
Collections.sort(loggerList, new LoggerComparator());
return loggerList;
}
另外我们发现logback还用了SPI机制
实现ServletContainerInitializer接口,在web容器初始化时将一个LogbackServletContextListener实例注册到servlet上下文中。
源码如下:
代码语言:javascript复制/**
* Allows for graceful shutdown of the {@link LoggerContext} associated with this web-app.
*
* @author Ceki Gulcu
* @since 1.1.10
*/
public class LogbackServletContextListener implements ServletContextListener {
ContextAwareBase contextAwareBase = new ContextAwareBase();
@Override
public void contextInitialized(ServletContextEvent sce) {
}
@Override
public void contextDestroyed(ServletContextEvent sce) {
ILoggerFactory iLoggerFactory = LoggerFactory.getILoggerFactory();
if (iLoggerFactory instanceof LoggerContext) {
LoggerContext loggerContext = (LoggerContext) iLoggerFactory;
contextAwareBase.setContext(loggerContext);
StatusViaSLF4JLoggerFactory.addInfo("About to stop " loggerContext.getClass().getCanonicalName() " [" loggerContext.getName() "]", this);
loggerContext.stop();
}
}
}
在上下文销毁时调用LoggerContext的stop函数,执行日志的停止操作。
代码语言:javascript复制 public void stop() {
reset();
fireOnStop();
resetAllListeners();
super.stop();
}
其中super.stop
代码语言:javascript复制 public void stop() {
// 这里不检查 "started" ,因为executor service使用了懒加载机制, 不是在starter函数中创建的
// 停止executor service
stopExecutorService();
started = false;
}
更多细节可以进入源码中查看。
如果觉得本文对你有帮助,欢迎点赞评论,欢迎关注我,我将努力创作更多更好的文章。