一、整體介紹
介紹:
The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks (e.g. java.util.logging, logback, log4j) allowing the end user to plug in the desired logging framework at deployment time.
工作模式:
sl4j-api結構圖
根據入口代碼來分析。
Logger logger = LoggerFactory.getLogger(logbackTest.class);
我們可以知道初始化,是從這個方法開始的。
首先入口代碼非常簡單
public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } public static ILoggerFactory getILoggerFactory() { 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://bugzilla.slf4j.org/show_bug.cgi?id=106 return TEMP_FACTORY; } throw new IllegalStateException("Unreachable code"); }
上面基本上就是整個調用流程的代碼了。第5行通過factory模式得到了一個日志工廠,第6行根據名字獲取日志logger。
第3個函數是主要的邏輯函數,根據INITIALIZATION_STATE 不同狀態,做不同的處理。
當INITIALIZATION_STATE ==0時,初始化日志框架【9行】
當INITIALIZATION_STATE ==1時,返回的TEMP_FACTORY,我們尅看下定義【15行】
static SubstituteLoggerFactory TEMP_FACTORY = new SubstituteLoggerFactory();
SubstituteLoggerFactory是什么尼,很簡單的結構,是由sl4j實現的一個。。,看一下文檔描述:
SubstituteLoggerFactory 簡單實現了ILoggerFactory接口,並總是返回一個單例的 NOPLogger實例。
作用:
* It used as a temporary substitute for the real ILoggerFactory during its
* auto-configuration which may re-enter LoggerFactory to obtain logger
* instances. See also http://bugzilla.slf4j.org/show_bug.cgi?id=106
當INITIALIZATION_STATE ==2時,返回的失敗信息【17行】,其中我們可以看下各種狀態枚舉定義,可以知道2對應的狀態是日志初始化失敗
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;
當INITIALIZATION_STATE ==3時,返回的失敗信息【19行】,顯然是日志初始化成功,獲取日志單例信息。
當INITIALIZATION_STATE ==4時,返回的失敗信息【21行】,我們看見返回會的是NOP_FALLBACK_FACTORY(沒有找到橋接jar)對象,看下定義
static NOPLoggerFactory NOP_FALLBACK_FACTORY = new NOPLoggerFactory(); /** * NOPLoggerFactory is an trivial implementation of {@link * ILoggerFactory} which always returns the unique instance of * NOPLogger. * * @author Ceki Gülcü */ public class NOPLoggerFactory implements ILoggerFactory { public NOPLoggerFactory() { // nothing to do } public Logger getLogger(String name) { return NOPLogger.NOP_LOGGER; } }
根據定義和和注釋,我們我們可以大膽猜測,4狀態就是什么都不做,NO Operator Logger Factory.僅僅是一個空實現。
官網描述:SINCE 1.6.0 If no binding is found on the class path, then SLF4J will default to a no-operation implementation.
二、核心代碼分析
重點分析這個方法performInitialization();
1 private final static void performInitialization() { 2 bind(); 3 if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 4 versionSanityCheck(); 5 } 6 } 7 private final static void bind() { 8 try { 9 Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet(); 10 reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); 11 // the next line does the binding 12 StaticLoggerBinder.getSingleton(); 13 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 14 reportActualBinding(staticLoggerBinderPathSet); 15 emitSubstituteLoggerWarning(); 16 } catch (NoClassDefFoundError ncde) { 17 String msg = ncde.getMessage(); 18 if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) { 19 INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION; 20 Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\"."); 21 Util.report("Defaulting to no-operation (NOP) logger implementation"); 22 Util.report("See " + NO_STATICLOGGERBINDER_URL 23 + " for further details."); 24 } else { 25 failedBinding(ncde); 26 throw ncde; 27 } 28 } catch (java.lang.NoSuchMethodError nsme) { 29 String msg = nsme.getMessage(); 30 if (msg != null && msg.indexOf("org.slf4j.impl.StaticLoggerBinder.getSingleton()") != -1) { 31 INITIALIZATION_STATE = FAILED_INITIALIZATION; 32 Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding."); 33 Util.report("Your binding is version 1.5.5 or earlier."); 34 Util.report("Upgrade your binding to version 1.6.x."); 35 } 36 throw nsme; 37 } catch (Exception e) { 38 failedBinding(e); 39 throw new IllegalStateException("Unexpected initialization failure", e); 40 } 41 }
首先進入bind方法,
bind方法第9行,findPossibleStaticLoggerBinderPathSet(),根據名字我們知道這個函數的功能是查找可能的StaticLoggerBinder路徑集合。
1 private static Set findPossibleStaticLoggerBinderPathSet() { 2 // use Set instead of list in order to deal with bug #138 3 // LinkedHashSet appropriate here because it preserves insertion order during iteration 4 Set staticLoggerBinderPathSet = new LinkedHashSet(); 5 try { 6 ClassLoader loggerFactoryClassLoader = LoggerFactory.class 7 .getClassLoader(); 8 Enumeration paths; 9 if (loggerFactoryClassLoader == null) { 10 paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH); 11 } else { 12 paths = loggerFactoryClassLoader 13 .getResources(STATIC_LOGGER_BINDER_PATH); 14 } 15 while (paths.hasMoreElements()) { 16 URL path = (URL) paths.nextElement(); 17 staticLoggerBinderPathSet.add(path); 18 } 19 } catch (IOException ioe) { 20 Util.report("Error getting resources from path", ioe); 21 } 22 return staticLoggerBinderPathSet; 23 }
我們可以看到都是在加載StaticLoggerBinder.class類
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
那么StaticLoggerBinder.class是干嘛的尼?傳送門:http://skyao.github.io/2014/07/21/slfj4-binding/,后續會對StaticLoggerBinder仔細分析。
bind方法第10行,假設找到多個StaticLoggerBinder.class,就提示輸出信息。
bind方法第12行,很關鍵的一行代碼,和第10行關聯起來了。獲取一個StaticLoggerBinder單例.
1 private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); 2 public static StaticLoggerBinder getSingleton() { 3 return SINGLETON; 4 } 5 static { 6 SINGLETON.init(); 7 } 8 void init() { 9 try { 10 try { 11 new ContextInitializer(defaultLoggerContext).autoConfig(); 12 } catch (JoranException je) { 13 Util.report("Failed to auto configure default logger context", je); 14 } 15 // logback-292 16 if(!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { 17 StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); 18 } 19 contextSelectorBinder.init(defaultLoggerContext, KEY); 20 initialized = true; 21 } catch (Throwable t) { 22 // we should never get here 23 Util.report("Failed to instantiate [" + LoggerContext.class.getName() 24 + "]", t); 25 } 26 }
可以看出其實就是一個單例,並初始化了。初始化的具體詳情,我們在后續會分析。這里我們只需要知道得到了一個StaticLoggerBinder單例對象即可。
bind方法14,15,16行主要是改變狀態,顯示警告信息等。
由此我們知道bind方法核心功能是加載了StaticLoggerBinder.class,並初始化了單例對象。
我們回到上面getILoggerFactory方法。
getILoggerFactory方法19行代碼:StaticLoggerBinder.getSingleton().getLoggerFactory();
即StaticLoggerBinder的getLoggerFactory方法,我們看一下定義。
1 public ILoggerFactory getLoggerFactory() { 2 if (!initialized) { 3 return defaultLoggerContext; 4 } 5 6 if (contextSelectorBinder.getContextSelector() == null) { 7 throw new IllegalStateException( 8 "contextSelector cannot be null. See also " + NULL_CS_URL); 9 } 10 return contextSelectorBinder.getContextSelector().getLoggerContext(); 11 }
在第二行判斷initialized狀態在init()方法第20行知道,在初始化完成后,即為true。所以getLoggerFactory方法會直接到第6行。
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton(); public static ContextSelectorStaticBinder getSingleton() { return singleton; } static ContextSelectorStaticBinder singleton = new ContextSelectorStaticBinder();
第6行獲取到一個ContextSelectorStaticBinder的實例。並且獲取getContextSelector方法
1 public ContextSelector getContextSelector() { 2 return contextSelector; 3 } 4 ContextSelector contextSelector; 5 public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException, 6 NoSuchMethodException, InstantiationException, IllegalAccessException, 7 InvocationTargetException { 8 if(this.key == null) { 9 this.key = key; 10 } else if (this.key != key) { 11 throw new IllegalAccessException("Only certain classes can access this method."); 12 } 13 14 15 String contextSelectorStr = OptionHelper 16 .getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR); 17 if (contextSelectorStr == null) { 18 contextSelector = new DefaultContextSelector(defaultLoggerContext); 19 } else if (contextSelectorStr.equals("JNDI")) { 20 // if jndi is specified, let's use the appropriate class 21 contextSelector = new ContextJNDISelector(defaultLoggerContext); 22 } else { 23 contextSelector = dynamicalContextSelector(defaultLoggerContext, 24 contextSelectorStr); 25 } 26 }
我們查詢代碼知道getContextSelector獲取的對象是在init()中方法賦值的。
在StaticLoggerBinder類中init()方法第19行代碼,進行了ContextSelectorStaticBinder的實例化。
contextSelectorBinder.getContextSelector()根據代碼知道,其實就是LoggerContext包裝對象。
第10代碼,contextSelectorBinder.getContextSelector().getLoggerContext();得到一個LoggerContext對象。
即,我們的getILoggerFactory的方法,得到了一個LoggerContext對象。
在getLogger方法第二行代碼,傳遞name獲取Logger對象。
1 public final Logger getLogger(final String name) { 2 3 if (name == null) { 4 throw new IllegalArgumentException("name argument cannot be null"); 5 } 6 7 // if we are asking for the root logger, then let us return it without 8 // wasting time 9 if (Logger.ROOT_LOGGER_NAME.equalsIgnoreCase(name)) { 10 return root; 11 } 12 13 int i = 0; 14 Logger logger = root; 15 16 // check if the desired logger exists, if it does, return it 17 // without further ado. 18 Logger childLogger = (Logger) loggerCache.get(name); 19 // if we have the child, then let us return it without wasting time 20 if (childLogger != null) { 21 return childLogger; 22 } 23 24 // if the desired logger does not exist, them create all the loggers 25 // in between as well (if they don't already exist) 26 String childName; 27 while (true) { 28 int h = LoggerNameUtil.getSeparatorIndexOf(name, i); 29 if (h == -1) { 30 childName = name; 31 } else { 32 childName = name.substring(0, h); 33 } 34 // move i left of the last point 35 i = h + 1; 36 synchronized (logger) { 37 childLogger = logger.getChildByName(childName); 38 if (childLogger == null) { 39 childLogger = logger.createChildByName(childName); 40 loggerCache.put(childName, childLogger); 41 incSize(); 42 } 43 } 44 logger = childLogger; 45 if (h == -1) { 46 return childLogger; 47 } 48 } 49 }
至此,整個流程就走完了。
下一章,我們會對整個sl4j架構進行分析。分析代碼的優缺點。
-------------------------------------------------------------------------華麗分割線,下面是StaticLoggerBinder代碼分析-------------------------------
在我們的源碼目錄中,並沒有這個類
那么他在哪里尼?這就是實現接口反轉和適配很重要的一個環節了,還記得我們開始第一幅圖么
,
sl4j-api會調用
看下類類定StaticLoggerBinder.class:binding一個實際的ILoggerFactory的實例。
* The binding of {@link LoggerFactory} class with an actual instance of
* {@link ILoggerFactory} is performed using information returned by this class.
來吧,一言不合就看源碼:
1 public class StaticLoggerBinder implements LoggerFactoryBinder { 2 3 /** 4 * Declare the version of the SLF4J API this implementation is compiled 5 * against. The value of this field is usually modified with each release. 6 */ 7 // to avoid constant folding by the compiler, this field must *not* be final 8 public static String REQUESTED_API_VERSION = "1.6"; // !final 9 10 final static String NULL_CS_URL = CoreConstants.CODES_URL + "#null_CS"; 11 12 /** 13 * The unique instance of this class. 14 */ 15 private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); 16 17 private static Object KEY = new Object(); 18 19 static { 20 SINGLETON.init(); 21 } 22 23 private boolean initialized = false; 24 private LoggerContext defaultLoggerContext = new LoggerContext(); 25 private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder 26 .getSingleton(); 27 28 private StaticLoggerBinder() { 29 defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); 30 } 31 32 public static StaticLoggerBinder getSingleton() { 33 return SINGLETON; 34 } 35 36 /** 37 * Package access for testing purposes. 38 */ 39 static void reset() { 40 SINGLETON = new StaticLoggerBinder(); 41 SINGLETON.init(); 42 } 43 44 /** 45 * Package access for testing purposes. 46 */ 47 void init() { 48 try { 49 try { 50 new ContextInitializer(defaultLoggerContext).autoConfig(); 51 } catch (JoranException je) { 52 Util.report("Failed to auto configure default logger context", je); 53 } 54 // logback-292 55 if(!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { 56 StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); 57 } 58 contextSelectorBinder.init(defaultLoggerContext, KEY); 59 initialized = true; 60 } catch (Throwable t) { 61 // we should never get here 62 Util.report("Failed to instantiate [" + LoggerContext.class.getName() 63 + "]", t); 64 } 65 } 66 67 public ILoggerFactory getLoggerFactory() { 68 if (!initialized) { 69 return defaultLoggerContext; 70 } 71 72 if (contextSelectorBinder.getContextSelector() == null) { 73 throw new IllegalStateException( 74 "contextSelector cannot be null. See also " + NULL_CS_URL); 75 } 76 return contextSelectorBinder.getContextSelector().getLoggerContext(); 77 } 78 79 public String getLoggerFactoryClassStr() { 80 return contextSelectorBinder.getClass().getName(); 81 } 82 83 }
先看下類圖: