OK,现在我们来研究slf4j的源码。前面我转载了一篇上善若水的博客,其实里面已经写的很详细了。没看过slf4j的源码之前我只是大致的了解了slf的执行,看过源码以后不得不惊叹大师的设计,所以这里自己也认真的来整理一遍。
slf4j源码目录结构
首先,我们从githud上下载slf4j的源码到本地,这个源码用maven管理的,是一个多项目的框架,原来的目录结构如下:
ok,这里好多的子项目,都是slf4j可以桥接的日志框架。我自己删除了好多,只剩下slf4j-api,slf4j-simple,slf4j-log4j12这3个子框架用来研究源码。
slf4j-simple源码研究
首先,我们研究一下slf4j-simple。该框架的目录结构如下:
这里我选中的3个才是一个简单的可以嫁入slf4j去桥接的日志框架的核心,必须有。
OK,然后我们可以自己写一个测试类,也可以直接用框架源码中的测试类。我这里直接拿来用了,测试代码如下:
- package org.slf4j;
-
- import java.io.PrintStream;
-
- import org.junit.After;
- import org.junit.Before;
- import org.junit.Test;
-
-
-
-
-
-
- public class InvocationTest
- {
- PrintStream old = System.err;
-
- @Before
- public void setUp() throws Exception
- {
- System.setErr(new SilentPrintStream(old));
- }
-
- @After
- public void tearDown() throws Exception
- {
-
- System.setErr(old);
- }
-
- @Test
- public void test()
- {
- Logger logger = LoggerFactory.getLogger("slf4j-simple-test");
- logger.info("Hello world.");
- }
- }
先不用配置文件,直接使用框架默认值来输出日志。来运行一把测试看下效果,junit绿条,控制台输出如下:
- SLF4J: Class path contains multiple SLF4J bindings.
- SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-simple/target/classes/org/slf4j/impl/StaticLoggerBinder.class]
- SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-api/target/classes/org/slf4j/impl/StaticLoggerBinder.class]
- SLF4J: See http:
- SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]
- [main] INFO slf4j-simple-test - Hello world.
关于该框架的3个核心类的代码我这里就不贴出来了,和我自己前面写的日志框架一个意思,感兴趣可以去研究我前面的博客。
在simpleLoggerFactory类getLogger()方法上打一个断点,来看下方法调用栈。这个方法的意思就是从日志工厂中获取一个logger实例。
OK,调用过程如下,其实slf源码也没有多少,直接从头到尾看也很快的。
![](https://img-blog.csdn.net/20160302214251135?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
那我们现在就从头到尾看下整个执行过程。
1,平时我们在用logger的时候都要通过工厂类来获取这个实例。代码如下:
- Logger logger = LoggerFactory.getLogger("slf4j-simple-test");
- logger.info("Hello world.");
2,上面工厂中获取logger实例的代码如下,中间这里用一个getILoggerFactory()方法来嫁入一层接口,所有的需要桥接到slf4j日志框架都需要实现ILoggerFactory接口。
-
-
-
-
-
-
-
-
-
- public static Logger getLogger(String name)
- {
- ILoggerFactory iLoggerFactory = getILoggerFactory();
- return iLoggerFactory.getLogger(name);
- }
ILoggerFactory接口源码如下:
- package org.slf4j;
-
-
-
-
-
-
- public interface ILoggerFactory
- {
-
- public Logger getLogger(String name);
- }
3,现在我们来看下
getILoggerFactory()获取ILoggerFactory工厂的方法。在slf的LoggerFactory中,定义了一堆的静态常量,用来做flag控制状态。顾名思义,如果工厂没被初始化,OK,执行performInitialization()方法。
- static final int UNINITIALIZED = 0;
- static final int ONGOING_INITIALIZATION = 1;
- static final int FAILED_INITIALIZATION = 2;
- static final int SUCCESSFUL_INITIALIZATION = 3;
- static final int NOP_FALLBACK_INITIALIZATION = 4;
- static int INITIALIZATION_STATE = UNINITIALIZED;
-
-
-
-
-
-
- public static ILoggerFactory getILoggerFactory()
- {
- 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:
- return SUBST_FACTORY;
- }
- throw new IllegalStateException("Unreachable code");
- }
4,现在来看下
performInitialization()方法,看看slf是如何做初始化的。
-
-
-
-
-
- private final static void performInitialization()
- {
- bind();
- if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION)
- {
- versionSanityCheck();
- }
- }
5,上面的代码比较比较简单,这里就不做赘述了。接下来我们来看bind(),这个方法是整个slf最核心的代码。
- private final static void bind()
- {
- try
- {
- Set<URL> staticLoggerBinderPathSet = null;
- if (!isAndroid())
- {
- staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
- reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
- }
-
- StaticLoggerBinder.getSingleton();
- INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
- reportActualBinding(staticLoggerBinderPathSet);
- fixSubstitutedLoggers();
- playRecordedEvents();
- SUBST_FACTORY.clear();
- }
- catch (NoClassDefFoundError ncde)
- {
- String msg = ncde.getMessage();
- if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg))
- {
- INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
- Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
- Util.report("Defaulting to no-operation (NOP) logger implementation");
- Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
- }
- else
- {
- failedBinding(ncde);
- throw ncde;
- }
- }
- catch (java.lang.NoSuchMethodError nsme)
- {
- String msg = nsme.getMessage();
- if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()"))
- {
- INITIALIZATION_STATE = FAILED_INITIALIZATION;
- Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
- Util.report("Your binding is version 1.5.5 or earlier.");
- Util.report("Upgrade your binding to version 1.6.x.");
- }
- throw nsme;
- }
- catch (Exception e)
- {
- failedBinding(e);
- throw new IllegalStateException("Unexpected initialization failure", e);
- }
- }
上面的catch不赘述了,核心代码就是下面3行:顾名思义,一行用来寻找可能的日志框架,一行用来输出找到的所有的日志框架信息,最后一行调用加载后的日志框架的staticLoggerBinder来获取该日志框架实例。
在这里要说一点,研究了这么多源码,其他的设计暂且不说,就开源代码的命名真心是一种学问,根本不用加注释,直接看名字就知道里面的代码在做什么了,这点也是很值得我们学习的。
- if (!isAndroid())
- {
- staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
- reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
- }
-
- StaticLoggerBinder.getSingleton();
6,这里我们重点来看下findPossibleStaticLoggerBinderPathSet()方法,我去,好长,不过总比写汉语注释的强。
-
- private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
-
- static Set<URL> findPossibleStaticLoggerBinderPathSet()
- {
-
-
- Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
- try
- {
- ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
- Enumeration<URL> paths;
- if (loggerFactoryClassLoader == null)
- {
- paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
- }
- else
- {
- paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
- }
- while (paths.hasMoreElements())
- {
- URL path = paths.nextElement();
- staticLoggerBinderPathSet.add(path);
- }
- }
- catch (IOException ioe)
- {
- Util.report("Error getting resources from path", ioe);
- }
- return staticLoggerBinderPathSet;
- }
7,上面的代码在理解上也没什么难度,首先定义一个path常量,指定加载类的路径,然后获取所有找见的StaticLoggerBinder类装入一个LinkedHashSet中。
这里用到一个类加载的知识,关于类加载我一会会专门整理一篇相关博客,个人觉得还是有点研究意义的。
8,OK,现在核心代码研究完了,在上面的第7步中,返回一个set后控制台打印输出,但是实际情况系统加载器只加载一个StaticLoggerBinder类,所以这里会加载第一个找见的该类,然后调用该类的getSingleton()方法,这个时候就切回到了slf4j-simple框架中了。该工厂类这里还用了一个单例来获取自己:
- public class StaticLoggerBinder implements LoggerFactoryBinder
- {
-
-
-
-
-
- private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
-
-
-
-
-
-
- public static final StaticLoggerBinder getSingleton()
- {
- return SINGLETON;
- }
9,初始化结束了,slf4j上面定义的一些旗标控制了正确的状态,然后这里就获取了相关日志框架的loggerFactory了。
- private final ILoggerFactory loggerFactory;
-
- private StaticLoggerBinder()
- {
- loggerFactory = new SimpleLoggerFactory();
- }
-
- public ILoggerFactory getLoggerFactory()
- {
- return loggerFactory;
- }
-
- public String getLoggerFactoryClassStr()
- {
- return loggerFactoryClassStr;
- }
10,成功的获取ILoggerFactory之后,调用该接口的getLogger()来获取相应的Logger实例就OK了。这里贴出slf4j-simple的LoggerFactory类源码,挺简单的。
- package org.slf4j.impl;
-
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentMap;
-
- import org.slf4j.Logger;
- import org.slf4j.ILoggerFactory;
-
- public class SimpleLoggerFactory implements ILoggerFactory
- {
-
- ConcurrentMap<String, Logger> loggerMap;
-
- public SimpleLoggerFactory()
- {
- loggerMap = new ConcurrentHashMap<String, Logger>();
-
- SimpleLogger.init();
- }
-
- public Logger getLogger(String name)
- {
- Logger simpleLogger = loggerMap.get(name);
- if (simpleLogger != null)
- {
- return simpleLogger;
- }
- else
- {
- Logger newInstance = new SimpleLogger(name);
- Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
- return oldInstance == null ? newInstance : oldInstance;
- }
- }
-
- void reset()
- {
- loggerMap.clear();
- }
- }
OK,到此为止,整个的日志框架初始化和获取logger实例都已经结束了,接下来就可以调用logger的info()等方法来输出日志了,剩下的我以前自己写的日志框架很详细,这里就不做赘述了。
slf-log4j12源码研究
简单的测试
同样的,我们现在来研究slf-log4j12的源码也是一个意思,直接打开框架源码中的测试类来执行测试,测试代码如下:
- package org.slf4j;
-
- import static org.junit.Assert.assertEquals;
-
- import org.junit.After;
- import org.junit.Before;
- import org.junit.Test;
-
- public class InvocationTest
- {
-
- ListAppender listAppender = new ListAppender();
- org.apache.log4j.Logger root;
-
- @Before
- public void setUp() throws Exception
- {
- root = org.apache.log4j.Logger.getRootLogger();
- root.addAppender(listAppender);
- }
-
- @After
- public void tearDown() throws Exception
- {
- root.getLoggerRepository().resetConfiguration();
- }
-
- @Test
- public void test()
- {
- Logger logger = LoggerFactory.getLogger("slf4j-log4j12-test");
- logger.debug("Hello world.");
- assertEquals(1, listAppender.list.size());
- }
-
- }
junit绿条,然后控制台输出如下:
- log4j: Parsing for [root] with value=[DEBUG, CONSOLE].
- log4j: Level token is [DEBUG].
- log4j: Category root set to DEBUG
- log4j: Parsing appender named "CONSOLE".
- log4j: Parsing layout options for "CONSOLE".
- log4j: Setting property [conversionPattern] to [%d [%t] %c - %m%n].
- log4j: End of parsing for "CONSOLE".
- log4j: Parsed "CONSOLE" options.
- log4j: Finished configuring.
- SLF4J: Class path contains multiple SLF4J bindings.
- SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-log4j12/target/classes/org/slf4j/impl/StaticLoggerBinder.class]
- SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-api/target/classes/org/slf4j/impl/StaticLoggerBinder.class]
- SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-simple/target/classes/org/slf4j/impl/StaticLoggerBinder.class]
- SLF4J: See http:
- SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]
- 2016-03-02 22:16:33,932 [main] slf4j-log4j12-test - Hello world.
当然我们也知道,使用log4j必须要有log4j.propertites文件。这里贴出配置:
- log4j.debug=true
- log4j.rootLogger=DEBUG, CONSOLE
-
- log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
- log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
- log4j.appender.CONSOLE.layout.ConversionPattern=%d [%t] %c - %m%n
-
- log4j.appender.RECURSIVE=org.slf4j.impl.RecursiveAppender
源码亮点
OK,如果是自己写这些代码的话,肯定有一个问题,就是说我们在桥接到slf4j的时候用的logger要使用slf定义的Logger接口,但是Log4j呢自己有自己的Logger类,他并没有实现slf的Logger接口啊,那怎么整合到一起呢?很简单,写一个适配呗。
关于适配模式,我前面的设计模式和Junit的相关整理中都有写的,大家如果感兴趣可以去看。
Log4jLoggerFactory该类实现了slf的ILoggerFactory接口,调用getLogger()方法返回一个Log4jLoggerAdapter实例。
Log4jLoggerAdapter该类实现了slf的Logger接口,然后里面封装了一个log4j的logger实例,调用info()等方法的时候实际上调用的log4j的Logger类的info()方法,这个设计和commong-logging一个道理,这里不做赘述了。
OK,这里贴出上面2个类的源码:
- package org.slf4j.impl;
-
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.ConcurrentMap;
-
- import org.apache.log4j.LogManager;
- import org.slf4j.helpers.Util;
- import org.slf4j.ILoggerFactory;
- import org.slf4j.Logger;
-
- public class Log4jLoggerFactory implements ILoggerFactory
- {
-
- private static final String LOG4J_DELEGATION_LOOP_URL = "http://www.slf4j.org/codes.html#log4jDelegationLoop";
-
-
- static
- {
- try
- {
- Class.forName("org.apache.log4j.Log4jLoggerFactory");
- String part1 = "Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. ";
- String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details.";
-
- Util.report(part1);
- Util.report(part2);
- throw new IllegalStateException(part1 + part2);
- }
- catch (ClassNotFoundException e)
- {
-
- }
- }
-
-
- ConcurrentMap<String, Logger> loggerMap;
-
- public Log4jLoggerFactory()
- {
- loggerMap = new ConcurrentHashMap<String, Logger>();
-
- org.apache.log4j.LogManager.getRootLogger();
- }
-
- public Logger getLogger(String name)
- {
- Logger slf4jLogger = loggerMap.get(name);
- if (slf4jLogger != null)
- {
- return slf4jLogger;
- }
- else
- {
- org.apache.log4j.Logger log4jLogger;
- if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
- log4jLogger = LogManager.getRootLogger();
- else
- log4jLogger = LogManager.getLogger(name);
-
- Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
-
- Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
- return oldInstance == null ? newInstance : oldInstance;
- }
- }
- }
- public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLogger, Serializable
- {
- private static final long serialVersionUID = 6182834493563598289L;
- final transient org.apache.log4j.Logger logger;
- final static String FQCN = Log4jLoggerAdapter.class.getName();
- final boolean traceCapable;
- Log4jLoggerAdapter(org.apache.log4j.Logger logger)
- {
- this.logger = logger;
- this.name = logger.getName();
- traceCapable = isTraceCapable();
- }
-
- public boolean isDebugEnabled()
- {
- return logger.isDebugEnabled();
- }
-
- public void debug(String msg)
- {
- logger.log(FQCN, Level.DEBUG, msg, null);
- }
-
- private boolean isTraceCapable()
- {
- try
- {
- logger.isTraceEnabled();
- return true;
- }
- catch (NoSuchMethodError e)
- {
- return false;
- }
- }
-
- public boolean isTraceEnabled()
- {
- if (traceCapable)
- {
- return logger.isTraceEnabled();
- }
- else
- {
- return logger.isDebugEnabled();
- }
- }
- }
总结
1,整个slf4j的设计比较简单,核心类有如下几个:
当然这里的staticLoggerBinder是一个空的实现,只有slf4j是不能跑日志的,比如要嫁入其他的日志框架才行的,具体的请看我上一篇关于slf4j的使用。
2,整个slf4j初始化过程也比较简单,过程如下图:
![](https://img-blog.csdn.net/20160302223946491?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
3,原理总结:
简单的说下它的原理,就是通过工厂类,提供一个用户的接口!用户可以通过这个外观接口,直接使用API实现日志的记录。而后面的具体实现由Slf4j来寻找加载.寻找的过程,就是通过类加载加载那个叫org/slf4j/impl/StaticLoggerBinder.class的文件,只要实现了这个文件的日志实现系统,都可以作为一种实现方式。如果找到很多种方式,那么就寻找一种默认的方式。
这就是日志接口的工作方式,简单高效,关键是完全解耦!不需要日志实现部分提供任何的修改配置,只需要符合接口的标准就可以加载进来。不同于Commons Logging的动态绑定机制,SLF4J则采用了一种静态绑定的机制。
4,自己写一个日志框架嫁入slf4j管理
也正是因为这个设计,SLF4J在classpath下只支持一个桥接包(slf4j-simple-<version>.jar、slf4j-log4j12-<version>.jar、slf4j-jdk14-<version>.jar、logback-classic-<version>.jar等)。如果在classpath下存在多个桥接包,则具体用哪个就要
看这几个桥接包的加载顺序了,实际中会使用先加载的桥接包。同时SLF4J会打印使用哪个桥接包,哪些桥接包没有使用。这种静态绑定的设计比Commons Logging在可扩展性上具有更加灵活的机制,对“可插拔”的支持也更加高效。
如果要支持一个新的Logging框架,Commons Logging需要通过在属性配置文件、或虚拟机属性中配置支持这个新的Logging框架的实现类(实现Log接口);而SLF4J则只需要编写一个五个相应的类:
1). 实现Logger接口的类
2). 实现ILoggerFactory接口的类
3). 实现LoggerFactoryBinder接口的类StaticLoggerBinder类(必须使用StaticLoggerBinder类名),并且存在一个静态的getSingleton()方法。
4). 实现MarkerFactoryBinder类的StaticMarkerBinder类(必须使用StaticMarkerBinder类名),可选。一般也会存在一个静态的SINGLETON字段,不过也是可选的。
5). 实现StaticMDCBinder类,可选。一般也会存在一个静态的SINGLETON字段,也可选。
注意:这里有一个陷阱,如果是自己写的日志框架,在嫁入slf4j的时候相关的staticLoggerBinder这个类的包不能随便写,一定要写成
org/slf4j/impl。本人就不幸了踩到这个坑了,研究完源码后也就明白这个坑来源何处了。
OK,关于slf4j的源码整理就整理到这里吧。