简单回顾门面模式html
slf4j是门面模式的典型应用,所以在讲slf4j前,咱们先简单回顾一下门面模式,java
门面模式,其核心为外部与一个子系统的通讯必须经过一个统一的外观对象进行,使得子系统更易于使用。用一张图来表示门面模式的结构为:android
门面模式的核心为Facade即门面对象,门面对象核心为几个点:apache
大体上来看,对门面模式的回顾到这里就能够了,开始接下来对SLF4J的学习。api
咱们为何要使用slf4japp
咱们为何要使用slf4j,举个例子:框架
咱们本身的系统中使用了logback这个日志系统 咱们的系统使用了A.jar,A.jar中使用的日志系统为log4j 咱们的系统又使用了B.jar,B.jar中使用的日志系统为slf4j-simple 这样,咱们的系统就不得不一样时支持并维护logback、log4j、slf4j-simple三种日志框架,很是不便。
解决这个问题的方式就是引入一个适配层,由适配层决定使用哪种日志系统,而调用端只须要作的事情就是打印日志而不须要关心如何打印日志,slf4j或者commons-logging就是这种适配层,slf4j是本文研究的对象。maven
从上面的描述,咱们必须清楚地知道一点:slf4j只是一个日志标准,并非日志系统的具体实现。理解这句话很是重要,slf4j只作两件事情:学习
slf4j-simple、logback都是slf4j的具体实现,log4j并不直接实现slf4j,可是有专门的一层桥接slf4j-log4j12来实现slf4j。ui
为了更理解slf4j,咱们先看例子,再读源码,相信读者朋友会对slf4j有更深入的认识。
slf4j应用举例
上面讲了,slf4j的直接/间接实现有slf4j-simple、logback、slf4j-log4j12,咱们先定义一个pom.xml,引入相关jar包:
1 <!-- 原文:五月的仓颉http://www.cnblogs.com/xrq730/p/8619156.html --> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 6 <groupId>org.xrq.log</groupId> 7 <artifactId>log-test</artifactId> 8 <version>1.0.0</version> 9 <packaging>jar</packaging> 10 11 <name>log-test</name> 12 <url>http://maven.apache.org</url> 13 14 <properties> 15 <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> 16 </properties> 17 18 <dependencies> 19 <dependency> 20 <groupId>junit</groupId> 21 <artifactId>junit</artifactId> 22 <version>4.11</version> 23 <scope>test</scope> 24 </dependency> 25 <dependency> 26 <groupId>org.slf4j</groupId> 27 <artifactId>slf4j-api</artifactId> 28 <version>1.7.25</version> 29 </dependency> 30 <dependency> 31 <groupId>ch.qos.logback</groupId> 32 <artifactId>logback-classic</artifactId> 33 <version>1.2.3</version> 34 </dependency> 35 <dependency> 36 <groupId>org.slf4j</groupId> 37 <artifactId>slf4j-simple</artifactId> 38 <version>1.7.25</version> 39 </dependency> 40 <dependency> 41 <groupId>log4j</groupId> 42 <artifactId>log4j</artifactId> 43 <version>1.2.17</version> 44 </dependency> 45 <dependency> 46 <groupId>org.slf4j</groupId> 47 <artifactId>slf4j-log4j12</artifactId> 48 <version>1.7.21</version> 49 </dependency> 50 </dependencies> 51 </project>
写一段简单的Java代码:
1 @Test 2 public void testSlf4j() { 3 Logger logger = LoggerFactory.getLogger(Object.class); 4 logger.error("123"); 5 }
接着咱们首先把上面pom.xml的第30行~第49行注释掉,即不引入任何slf4j的实现类,运行Test方法,咱们看一下控制台的输出为:
看到没有任何日志的输出,这验证了咱们的观点:slf4j不提供日志的具体实现,只有slf4j是没法打印日志的。
接着打开logback-classic的注释,运行Test方法,咱们看一下控制台的输出为:
看到咱们只要引入了一个slf4j的具体实现类,便可使用该日志框架输出日志。
最后作一个测验,咱们把全部日志打开,引入logback-classic、slf4j-simple、log4j,运行Test方法,控制台输出为:
和上面的差异是,能够输出日志,可是会输出一些告警日志,提示咱们同时引入了多个slf4j的实现,而后选择其中的一个做为咱们使用的日志系统。
从例子咱们能够得出一个重要的结论,即slf4j的做用:只要全部代码都使用门面对象slf4j,咱们就不须要关心其具体实现,最终全部地方使用一种具体实现便可,更换、维护都很是方便。
slf4j实现原理
上面看了slf4j的示例,下面研究一下slf4j的实现,咱们只关注重点代码。
slf4j的用法就是常年不变的一句"Logger logger = LoggerFactory.getLogger(Object.class);",可见这里就是经过LoggerFactory去拿slf4j提供的一个Logger接口的具体实现而已,LoggerFactory的getLogger的方法实现为:
1 public static Logger getLogger(Class<?> clazz) { 2 Logger logger = getLogger(clazz.getName()); 3 if (DETECT_LOGGER_NAME_MISMATCH) { 4 Class<?> autoComputedCallingClass = Util.getCallingClass(); 5 if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 6 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 7 autoComputedCallingClass.getName())); 8 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 9 } 10 } 11 return logger; 12 }
从第2行开始跟代码,一直跟到LoggerFactory的bind()方法:
1 private final static void bind() { 2 try { 3 Set<URL> staticLoggerBinderPathSet = null; 4 // skip check under android, see also 5 // http://jira.qos.ch/browse/SLF4J-328 6 if (!isAndroid()) { 7 staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 8 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); 9 } 10 // the next line does the binding 11 StaticLoggerBinder.getSingleton(); 12 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 13 reportActualBinding(staticLoggerBinderPathSet); 14 fixSubstituteLoggers(); 15 replayEvents(); 16 // release all resources in SUBST_FACTORY 17 SUBST_FACTORY.clear(); 18 } catch (NoClassDefFoundError ncde) { 19 String msg = ncde.getMessage(); 20 if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { 21 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; 22 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); 23 Util.report("Defaulting to no-operation (NOP) logger implementation"); 24 Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details."); 25 } else { 26 failedBinding(ncde); 27 throw ncde; 28 } 29 } catch (java.lang.NoSuchMethodError nsme) { 30 String msg = nsme.getMessage(); 31 if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) { 32 INITIALIZATION_STATE = FAILED_INITIALIZATION; 33 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); 34 Util.report("Your binding is version 1.5.5 or earlier."); 35 Util.report("Upgrade your binding to version 1.6.x."); 36 } 37 throw nsme; 38 } catch (Exception e) { 39 failedBinding(e); 40 throw new IllegalStateException("Unexpected initialization failure", e); 41 } 42 }
这个地方第7行是一个关键,看一下代码:
1 static Set<URL> findPossibleStaticLoggerBinderPathSet() { 2 // use Set instead of list in order to deal with bug #138 3 // LinkedHashSet appropriate here because it preserves insertion order 4 // during iteration 5 Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>(); 6 try { 7 ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader(); 8 Enumeration<URL> paths; 9 if (loggerFactoryClassLoader == null) { 10 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 11 } else { 12 paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 13 } 14 while (paths.hasMoreElements()) { 15 URL path = paths.nextElement(); 16 staticLoggerBinderPathSet.add(path); 17 } 18 } catch (IOException ioe) { 19 Util.report("Error getting resources from path", ioe); 20 } 21 return staticLoggerBinderPathSet; 22 }
这个地方重点其实就是第12行的代码,getLogger的时候会去classpath下找STATIC_LOGGER_BINDER_PATH,STATIC_LOGGER_BINDER_PATH值为"org/slf4j/impl/StaticLoggerBinder.class",即全部slf4j的实现,在提供的jar包路径下,必定是有"org/slf4j/impl/StaticLoggerBinder.class"存在的,咱们能够看一下:
咱们不能避免在系统中同时引入多个slf4j的实现,因此接收的地方是一个Set。你们应该注意到,上部分在演示同时引入logback、slf4j-simple、log4j的时候会有警告:
这就是由于有三个"org/slf4j/impl/StaticLoggerBinder.class"存在的缘由,此时reportMultipleBindingAmbiguity方法控制台输出语句:
1 private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) { 2 if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { 3 Util.report("Class path contains multiple SLF4J bindings."); 4 for (URL path : binderPathSet) { 5 Util.report("Found binding in [" + path + "]"); 6 } 7 Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation."); 8 } 9 }
那网友朋友可能会问,同时存在三个"org/slf4j/impl/StaticLoggerBinder.class"怎么办?首先肯定的是这不会致使启动报错,其次在这种状况下编译期间,编译器会选择其中一个StaticLoggerBinder.class进行绑定,这个地方sfl4j也在reportActualBinding方法中报告了绑定的是哪一个日志框架:
1 private static void reportActualBinding(Set<URL> binderPathSet) { 2 // binderPathSet can be null under Android 3 if (binderPathSet != null && isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) { 4 Util.report("Actual binding is of type [" + StaticLoggerBinder.getSingleton().getLoggerFactoryClassStr() + "]"); 5 } 6 }
对照上面的截图,看最后一行,确实是"Actual binding is of type..."这句。
最后StaticLoggerBinder就比较简单了,不一样的StaticLoggerBinder其getLoggerFactory实现不一样,拿到ILoggerFactory以后调用一下getLogger即拿到了具体的Logger,可使用Logger进行日志输出。