slf4j原理

OK,现在我们来研究slf4j的源码。前面我转载了一篇上善若水的博客,其实里面已经写的很详细了。没看过slf4j的源码之前我只是大致的了解了slf的执行,看过源码以后不得不惊叹大师的设计,所以这里自己也认真的来整理一遍。

slf4j源码目录结构

首先,我们从githud上下载slf4j的源码到本地,这个源码用maven管理的,是一个多项目的框架,原来的目录结构如下:

 

ok,这里好多的子项目,都是slf4j可以桥接的日志框架。我自己删除了好多,只剩下slf4j-api,slf4j-simple,slf4j-log4j12这3个子框架用来研究源码。


slf4j-simple源码研究

首先,我们研究一下slf4j-simple。该框架的目录结构如下:

   这里我选中的3个才是一个简单的可以嫁入slf4j去桥接的日志框架的核心,必须有。



OK,然后我们可以自己写一个测试类,也可以直接用框架源码中的测试类。我这里直接拿来用了,测试代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package org.slf4j;  
  2.   
  3. import java.io.PrintStream;  
  4.   
  5. import org.junit.After;  
  6. import org.junit.Before;  
  7. import org.junit.Test;  
  8.   
  9. /** 
  10.  * @创建作者: LinkinPark 
  11.  * @创建时间: 2016年3月2日 
  12.  * @功能描述: 测试类,slf-simple默认只输出info级别的以上的。 
  13.  */  
  14. public class InvocationTest  
  15. {  
  16.     PrintStream old = System.err;  
  17.   
  18.     @Before  
  19.     public void setUp() throws Exception  
  20.     {  
  21.         System.setErr(new SilentPrintStream(old));  
  22.     }  
  23.   
  24.     @After  
  25.     public void tearDown() throws Exception  
  26.     {  
  27.   
  28.         System.setErr(old);  
  29.     }  
  30.   
  31.     @Test  
  32.     public void test()  
  33.     {  
  34.         Logger logger = LoggerFactory.getLogger("slf4j-simple-test");  
  35.         logger.info("Hello world.");  
  36.     }  
  37. }  

先不用配置文件,直接使用框架默认值来输出日志。来运行一把测试看下效果,junit绿条,控制台输出如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. SLF4J: Class path contains multiple SLF4J bindings.  
  2. SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-simple/target/classes/org/slf4j/impl/StaticLoggerBinder.class]  
  3. SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-api/target/classes/org/slf4j/impl/StaticLoggerBinder.class]  
  4. SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.  
  5. SLF4J: Actual binding is of type [org.slf4j.impl.SimpleLoggerFactory]  
  6. [main] INFO slf4j-simple-test - Hello world.  

关于该框架的3个核心类的代码我这里就不贴出来了,和我自己前面写的日志框架一个意思,感兴趣可以去研究我前面的博客。

在simpleLoggerFactory类getLogger()方法上打一个断点,来看下方法调用栈。这个方法的意思就是从日志工厂中获取一个logger实例。

OK,调用过程如下,其实slf源码也没有多少,直接从头到尾看也很快的。


那我们现在就从头到尾看下整个执行过程。

1,平时我们在用logger的时候都要通过工厂类来获取这个实例。代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. Logger logger = LoggerFactory.getLogger("slf4j-simple-test");  
  2.         logger.info("Hello world.");  


2,上面工厂中获取logger实例的代码如下,中间这里用一个getILoggerFactory()方法来嫁入一层接口,所有的需要桥接到slf4j日志框架都需要实现ILoggerFactory接口。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * @创建时间: 2016年3月2日 
  3.      * @相关参数: @param name 日志名称 
  4.      * @相关参数: @return 
  5.      * @功能描述: 获取一个logger实例。 
  6.      * <p> 
  7.      * 这里嫁入一层ILoggerFactory,所有的日志框架都需实现该接口 
  8.      * </p> 
  9.      */  
  10.     public static Logger getLogger(String name)  
  11.     {  
  12.         ILoggerFactory iLoggerFactory = getILoggerFactory();  
  13.         return iLoggerFactory.getLogger(name);  
  14.     }  

ILoggerFactory接口源码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package org.slf4j;  
  2.   
  3. /** 
  4.  * @创建作者: LinkinPark 
  5.  * @创建时间: 2016年3月2日 
  6.  * @功能描述: slf框架内部的日志工厂 
  7.  */  
  8. public interface ILoggerFactory  
  9. {  
  10.     // 各种桥接的日志框架都是通过该方法返回的Logger实例  
  11.     public Logger getLogger(String name);  
  12. }  


3,现在我们来看下  

getILoggerFactory()获取ILoggerFactory工厂的方法。在slf的LoggerFactory中,定义了一堆的静态常量,用来做flag控制状态。顾名思义,如果工厂没被初始化,OK,执行performInitialization()方法。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. static final int UNINITIALIZED = 0;  
  2.     static final int ONGOING_INITIALIZATION = 1;  
  3.     static final int FAILED_INITIALIZATION = 2;  
  4.     static final int SUCCESSFUL_INITIALIZATION = 3;  
  5.     static final int NOP_FALLBACK_INITIALIZATION = 4;  
  6.     static int INITIALIZATION_STATE = UNINITIALIZED;  
  7.   
  8.     /** 
  9.      * @创建时间: 2016年3月2日 
  10.      * @相关参数: @return 
  11.      * @功能描述: slf核心代码 
  12.      */  
  13.     public static ILoggerFactory getILoggerFactory()  
  14.     {  
  15.         if (INITIALIZATION_STATE == UNINITIALIZED)  
  16.         {  
  17.             synchronized (LoggerFactory.class)  
  18.             {  
  19.                 if (INITIALIZATION_STATE == UNINITIALIZED)  
  20.                 {  
  21.                     INITIALIZATION_STATE = ONGOING_INITIALIZATION;  
  22.                     performInitialization();  
  23.                 }  
  24.             }  
  25.         }  
  26.   
  27.         switch (INITIALIZATION_STATE)  
  28.         {  
  29.         case SUCCESSFUL_INITIALIZATION:  
  30.             return StaticLoggerBinder.getSingleton().getLoggerFactory();  
  31.         case NOP_FALLBACK_INITIALIZATION:  
  32.             return NOP_FALLBACK_FACTORY;  
  33.         case FAILED_INITIALIZATION:  
  34.             throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);  
  35.         case ONGOING_INITIALIZATION:  
  36.             return SUBST_FACTORY;  
  37.         }  
  38.         throw new IllegalStateException("Unreachable code");  
  39.     }  

4,现在来看下

performInitialization()方法,看看slf是如何做初始化的。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. /** 
  2.      * @创建时间: 2016年3月2日 
  3.      * @相关参数:  
  4.      * @功能描述: 绑定日志框架+校验日志框架版本号 
  5.      */  
  6.     private final static void performInitialization()  
  7.     {  
  8.         bind();  
  9.         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION)  
  10.         {  
  11.             versionSanityCheck();  
  12.         }  
  13.     }  


5,上面的代码比较比较简单,这里就不做赘述了。接下来我们来看bind(),这个方法是整个slf最核心的代码。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private final static void bind()  
  2.     {  
  3.         try  
  4.         {  
  5.             Set<URL> staticLoggerBinderPathSet = null;  
  6.             if (!isAndroid())  
  7.             {  
  8.                 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();  
  9.                 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);  
  10.             }  
  11.             // the next line does the binding  
  12.             StaticLoggerBinder.getSingleton();  
  13.             INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;  
  14.             reportActualBinding(staticLoggerBinderPathSet);  
  15.             fixSubstitutedLoggers();  
  16.             playRecordedEvents();  
  17.             SUBST_FACTORY.clear();  
  18.         }  
  19.         catch (NoClassDefFoundError ncde)  
  20.         {  
  21.             String msg = ncde.getMessage();  
  22.             if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg))  
  23.             {  
  24.                 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;  
  25.                 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");  
  26.                 Util.report("Defaulting to no-operation (NOP) logger implementation");  
  27.                 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");  
  28.             }  
  29.             else  
  30.             {  
  31.                 failedBinding(ncde);  
  32.                 throw ncde;  
  33.             }  
  34.         }  
  35.         catch (java.lang.NoSuchMethodError nsme)  
  36.         {  
  37.             String msg = nsme.getMessage();  
  38.             if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()"))  
  39.             {  
  40.                 INITIALIZATION_STATE = FAILED_INITIALIZATION;  
  41.                 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");  
  42.                 Util.report("Your binding is version 1.5.5 or earlier.");  
  43.                 Util.report("Upgrade your binding to version 1.6.x.");  
  44.             }  
  45.             throw nsme;  
  46.         }  
  47.         catch (Exception e)  
  48.         {  
  49.             failedBinding(e);  
  50.             throw new IllegalStateException("Unexpected initialization failure", e);  
  51.         }  
  52.     }  

上面的catch不赘述了,核心代码就是下面3行:顾名思义,一行用来寻找可能的日志框架,一行用来输出找到的所有的日志框架信息,最后一行调用加载后的日志框架的staticLoggerBinder来获取该日志框架实例。

在这里要说一点,研究了这么多源码,其他的设计暂且不说,就开源代码的命名真心是一种学问,根本不用加注释,直接看名字就知道里面的代码在做什么了,这点也是很值得我们学习的。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. if (!isAndroid())  
  2.             {  
  3.                 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();  
  4.                 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);  
  5.             }  
  6.             // the next line does the binding  
  7.             StaticLoggerBinder.getSingleton();  


6,这里我们重点来看下findPossibleStaticLoggerBinderPathSet()方法,我去,好长,不过总比写汉语注释的强。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // We need to use the name of the StaticLoggerBinder class, but we can't reference the class itself.  
  2.     private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";  
  3.   
  4.     static Set<URL> findPossibleStaticLoggerBinderPathSet()  
  5.     {  
  6.         // use Set instead of list in order to deal with bug #138  
  7.         // LinkedHashSet appropriate here because it preserves insertion order during iteration  
  8.         Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();  
  9.         try  
  10.         {  
  11.             ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();  
  12.             Enumeration<URL> paths;  
  13.             if (loggerFactoryClassLoader == null)  
  14.             {  
  15.                 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);  
  16.             }  
  17.             else  
  18.             {  
  19.                 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);  
  20.             }  
  21.             while (paths.hasMoreElements())  
  22.             {  
  23.                 URL path = paths.nextElement();  
  24.                 staticLoggerBinderPathSet.add(path);  
  25.             }  
  26.         }  
  27.         catch (IOException ioe)  
  28.         {  
  29.             Util.report("Error getting resources from path", ioe);  
  30.         }  
  31.         return staticLoggerBinderPathSet;  
  32.     }  

7,上面的代码在理解上也没什么难度,首先定义一个path常量,指定加载类的路径,然后获取所有找见的StaticLoggerBinder类装入一个LinkedHashSet中。

这里用到一个类加载的知识,关于类加载我一会会专门整理一篇相关博客,个人觉得还是有点研究意义的。

8,OK,现在核心代码研究完了,在上面的第7步中,返回一个set后控制台打印输出,但是实际情况系统加载器只加载一个StaticLoggerBinder类,所以这里会加载第一个找见的该类,然后调用该类的getSingleton()方法,这个时候就切回到了slf4j-simple框架中了。该工厂类这里还用了一个单例来获取自己:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public class StaticLoggerBinder implements LoggerFactoryBinder  
  2. {  
  3.   
  4.     /** 
  5.      * The unique instance of this class. 
  6.      *  
  7.      */  
  8.     private static final StaticLoggerBinder SINGLETON = new StaticLoggerBinder();  
  9.   
  10.     /** 
  11.      * Return the singleton of this class. 
  12.      *  
  13.      * @return the StaticLoggerBinder singleton 
  14.      */  
  15.     public static final StaticLoggerBinder getSingleton()  
  16.     {  
  17.         return SINGLETON;  
  18.     }  

9,初始化结束了,slf4j上面定义的一些旗标控制了正确的状态,然后这里就获取了相关日志框架的loggerFactory了。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private final ILoggerFactory loggerFactory;  
  2.   
  3.     private StaticLoggerBinder()  
  4.     {  
  5.         loggerFactory = new SimpleLoggerFactory();  
  6.     }  
  7.   
  8.     public ILoggerFactory getLoggerFactory()  
  9.     {  
  10.         return loggerFactory;  
  11.     }  
  12.   
  13.     public String getLoggerFactoryClassStr()  
  14.     {  
  15.         return loggerFactoryClassStr;  
  16.     }  

10,成功的获取ILoggerFactory之后,调用该接口的getLogger()来获取相应的Logger实例就OK了。这里贴出slf4j-simple的LoggerFactory类源码,挺简单的。

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package org.slf4j.impl;  
  2.   
  3. import java.util.concurrent.ConcurrentHashMap;  
  4. import java.util.concurrent.ConcurrentMap;  
  5.   
  6. import org.slf4j.Logger;  
  7. import org.slf4j.ILoggerFactory;  
  8.   
  9. public class SimpleLoggerFactory implements ILoggerFactory  
  10. {  
  11.   
  12.     ConcurrentMap<String, Logger> loggerMap;  
  13.   
  14.     public SimpleLoggerFactory()  
  15.     {  
  16.         loggerMap = new ConcurrentHashMap<String, Logger>();  
  17.         // logger类初始化  
  18.         SimpleLogger.init();  
  19.     }  
  20.   
  21.     public Logger getLogger(String name)  
  22.     {  
  23.         Logger simpleLogger = loggerMap.get(name);  
  24.         if (simpleLogger != null)  
  25.         {  
  26.             return simpleLogger;  
  27.         }  
  28.         else  
  29.         {  
  30.             Logger newInstance = new SimpleLogger(name);  
  31.             Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);  
  32.             return oldInstance == null ? newInstance : oldInstance;  
  33.         }  
  34.     }  
  35.   
  36.     void reset()  
  37.     {  
  38.         loggerMap.clear();  
  39.     }  
  40. }  

OK,到此为止,整个的日志框架初始化和获取logger实例都已经结束了,接下来就可以调用logger的info()等方法来输出日志了,剩下的我以前自己写的日志框架很详细,这里就不做赘述了。


slf-log4j12源码研究

简单的测试

同样的,我们现在来研究slf-log4j12的源码也是一个意思,直接打开框架源码中的测试类来执行测试,测试代码如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package org.slf4j;  
  2.   
  3. import static org.junit.Assert.assertEquals;  
  4.   
  5. import org.junit.After;  
  6. import org.junit.Before;  
  7. import org.junit.Test;  
  8.   
  9. public class InvocationTest  
  10. {  
  11.   
  12.     ListAppender listAppender = new ListAppender();  
  13.     org.apache.log4j.Logger root;  
  14.   
  15.     @Before  
  16.     public void setUp() throws Exception  
  17.     {  
  18.         root = org.apache.log4j.Logger.getRootLogger();  
  19.         root.addAppender(listAppender);  
  20.     }  
  21.   
  22.     @After  
  23.     public void tearDown() throws Exception  
  24.     {  
  25.         root.getLoggerRepository().resetConfiguration();  
  26.     }  
  27.   
  28.     @Test  
  29.     public void test()  
  30.     {  
  31.         Logger logger = LoggerFactory.getLogger("slf4j-log4j12-test");  
  32.         logger.debug("Hello world.");  
  33.         assertEquals(1, listAppender.list.size());  
  34.     }  
  35.   
  36. }  
junit绿条,然后控制台输出如下:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. log4j: Parsing for [root] with value=[DEBUG, CONSOLE].  
  2. log4j: Level token is [DEBUG].  
  3. log4j: Category root set to DEBUG  
  4. log4j: Parsing appender named "CONSOLE".  
  5. log4j: Parsing layout options for "CONSOLE".  
  6. log4j: Setting property [conversionPattern] to [%d [%t] %c - %m%n].  
  7. log4j: End of parsing for "CONSOLE".  
  8. log4j: Parsed "CONSOLE" options.  
  9. log4j: Finished configuring.  
  10. SLF4J: Class path contains multiple SLF4J bindings.  
  11. SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-log4j12/target/classes/org/slf4j/impl/StaticLoggerBinder.class]  
  12. SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-api/target/classes/org/slf4j/impl/StaticLoggerBinder.class]  
  13. SLF4J: Found binding in [file:/Users/LinkinPark/WorkSpace/slf4j-frame-slf4j/slf4j-simple/target/classes/org/slf4j/impl/StaticLoggerBinder.class]  
  14. SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.  
  15. SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]  
  16. 2016-03-02 22:16:33,932 [main] slf4j-log4j12-test - Hello world.  
当然我们也知道,使用log4j必须要有log4j.propertites文件。这里贴出配置:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. log4j.debug=true  
  2. log4j.rootLogger=DEBUG, CONSOLE  
  3.   
  4. log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender  
  5. log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout  
  6. log4j.appender.CONSOLE.layout.ConversionPattern=%d [%t] %c - %m%n  
  7.   
  8. 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个类的源码:

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. package org.slf4j.impl;  
  2.   
  3. import java.util.concurrent.ConcurrentHashMap;  
  4. import java.util.concurrent.ConcurrentMap;  
  5.   
  6. import org.apache.log4j.LogManager;  
  7. import org.slf4j.helpers.Util;  
  8. import org.slf4j.ILoggerFactory;  
  9. import org.slf4j.Logger;  
  10.   
  11. public class Log4jLoggerFactory implements ILoggerFactory  
  12. {  
  13.   
  14.     private static final String LOG4J_DELEGATION_LOOP_URL = "http://www.slf4j.org/codes.html#log4jDelegationLoop";  
  15.   
  16.     // check for delegation loops  
  17.     static  
  18.     {  
  19.         try  
  20.         {  
  21.             Class.forName("org.apache.log4j.Log4jLoggerFactory");  
  22.             String part1 = "Detected both log4j-over-slf4j.jar AND bound slf4j-log4j12.jar on the class path, preempting StackOverflowError. ";  
  23.             String part2 = "See also " + LOG4J_DELEGATION_LOOP_URL + " for more details.";  
  24.   
  25.             Util.report(part1);  
  26.             Util.report(part2);  
  27.             throw new IllegalStateException(part1 + part2);  
  28.         }  
  29.         catch (ClassNotFoundException e)  
  30.         {  
  31.             // this is the good case  
  32.         }  
  33.     }  
  34.   
  35.     // key: name (String), value: a Log4jLoggerAdapter;  
  36.     ConcurrentMap<String, Logger> loggerMap;  
  37.   
  38.     public Log4jLoggerFactory()  
  39.     {  
  40.         loggerMap = new ConcurrentHashMap<String, Logger>();  
  41.         // force log4j to initialize  
  42.         org.apache.log4j.LogManager.getRootLogger();  
  43.     }  
  44.   
  45.     public Logger getLogger(String name)  
  46.     {  
  47.         Logger slf4jLogger = loggerMap.get(name);  
  48.         if (slf4jLogger != null)  
  49.         {  
  50.             return slf4jLogger;  
  51.         }  
  52.         else  
  53.         {  
  54.             org.apache.log4j.Logger log4jLogger;  
  55.             if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))  
  56.                 log4jLogger = LogManager.getRootLogger();  
  57.             else  
  58.                 log4jLogger = LogManager.getLogger(name);  
  59.   
  60.             Logger newInstance = new Log4jLoggerAdapter(log4jLogger);  
  61.             // putIfAbsent方法有意思,如果map缺席该key就扔进去,注意返回的是原来的map.get(key)。  
  62.             Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);  
  63.             return oldInstance == null ? newInstance : oldInstance;  
  64.         }  
  65.     }  
  66. }  

[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public final class Log4jLoggerAdapter extends MarkerIgnoringBase implements LocationAwareLogger, Serializable  
  2. {  
  3.     private static final long serialVersionUID = 6182834493563598289L;  
  4.     final transient org.apache.log4j.Logger logger;  
  5.     final static String FQCN = Log4jLoggerAdapter.class.getName();  
  6.     final boolean traceCapable; // 还有一个旗标,控制输出debug级别日志或者trace级别  
  7.     Log4jLoggerAdapter(org.apache.log4j.Logger logger)  
  8.     {  
  9.         this.logger = logger;  
  10.         this.name = logger.getName();  
  11.         traceCapable = isTraceCapable();  
  12.     }  
  13.       
  14.     public boolean isDebugEnabled()  
  15.     {  
  16.         return logger.isDebugEnabled();  
  17.     }  
  18.       
  19.     public void debug(String msg)  
  20.     {  
  21.         logger.log(FQCN, Level.DEBUG, msg, null);  
  22.     }  
  23.   
  24.     private boolean isTraceCapable()  
  25.     {  
  26.         try  
  27.         {  
  28.             logger.isTraceEnabled();  
  29.             return true;  
  30.         }  
  31.         catch (NoSuchMethodError e)  
  32.         {  
  33.             return false;  
  34.         }  
  35.     }  
  36.   
  37.     public boolean isTraceEnabled()  
  38.     {  
  39.         if (traceCapable)  
  40.         {  
  41.             return logger.isTraceEnabled();  
  42.         }  
  43.         else  
  44.         {  
  45.             return logger.isDebugEnabled();  
  46.         }  
  47.     }  
  48. }  


总结

1,整个slf4j的设计比较简单,核心类有如下几个:

 当然这里的staticLoggerBinder是一个空的实现,只有slf4j是不能跑日志的,比如要嫁入其他的日志框架才行的,具体的请看我上一篇关于slf4j的使用。

2,整个slf4j初始化过程也比较简单,过程如下图:



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的源码整理就整理到这里吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值