上一篇博客介紹了slf4j-api的核心類和接口,以及如何和日志實現框架對接的。簡而言之就是通過下面這行代碼:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
每個實現門面slf4j-api的日志實現框架都提供了org.slf4j.impl.StaticLoggerBinder類,通過委托該類返回一個ILoggerFactory的實現類,從而和具體的日志實現框架進行綁定。
這篇博客就來講述一下,logback的StaticLoggerBinder如何提供ILoggerFactory的實現類。
從圖中可以看到,StaticLoggerBinder實現了LoggerFactory接口,提供了getLoggerFactory()方法。LoggerContext實現了ILoggerFactory接口,提供了getLogger(String name)方法。StaticLoggerBinder擁有成員變量LoggerContext和ContextSelectorStaticBinder。StaticLoggerBinder委托ContextInitializer來初始化LoggerContext和委托ContextSelectorStaticBinder來選擇一個上下文。
下面就來具體看看StaticLoggerBinder的代碼 :
/** * The unique instance of this class. * 餓漢單例 * */ private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder(); /** * 私有靜態對象 * 只有該類能訪問的對象 * 用來做權限控制 后續只能該類來調用 */ private static Object KEY = new Object(); /** * 初始化該對象 */ static { SINGLETON.init(); } /** * 該對象是否初始化 */ private boolean initialized = false; /** * ILoggerFactory的實現類 */ private LoggerContext defaultLoggerContext = new LoggerContext(); /** * 上下文選擇器綁定者 * 通過 把defaultLoggerContext傳遞給ContextSelectorStaticBinder * ContextSelectorStaticBinder 選擇創建不同的ContextSelector 來獲取不同環境的LoggerContext * 選擇策略 ContextSelector接口的實現有 ContextJNDISelector 和 DefaultContextSelector 策略模式的應用 * ContextSelectorStaticBinder 作用主要是來選擇策略 * 這種實現方式可以用來借鑒 */ private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() { //為上下文設置名稱 defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME); }
/**
*提供getSingleton()方法是對接slf4j的強制要求
*/
public static StaticLoggerBinder getSingleton() { return SINGLETON; } /** * Package access for testing purposes. * 重置 */ static void reset() { SINGLETON = new StaticLoggerBinder(); SINGLETON.init(); } /** * Package access for testing purposes. * * 1.委托ContextInitializer對象初始化LoggerContext * 2.判斷上下文是否有狀態監聽器 如果沒有就用StatusPrinter打印上下文中的警告和錯誤狀態 * 3.把defaultLoggerContext傳遞給ContextSelectorStaticBinder,選擇一個contextSelector 初始化成員變量 * 在getLoggerFactory()方法中通過contextSelectorBinder.getContextSelector().getLoggerContext()來獲取loggerContext */ void init() { try { try { //委托ContextInitializer類對defaultLoggerContext進行初始化 //這里如果找到了任一配置文件,就會根據配置文件去初始化LoggerContext,如果沒找到,會使用默認配置。 new ContextInitializer(defaultLoggerContext).autoConfig(); } catch (JoranException je) { Util.report("Failed to auto configure default logger context", je); } // logback-292 if (!StatusUtil.contextHasStatusListener(defaultLoggerContext)) { StatusPrinter.printInCaseOfErrorsOrWarnings(defaultLoggerContext); } //對ContextSelectorStaticBinder類進行初始化 contextSelectorBinder.init(defaultLoggerContext, KEY); initialized = true; } catch (Exception t) { // see LOGBACK-1159 Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t); } } /** * LoggerFactoryBinder接口的實現方法 獲取ILoggerFactory的實現類 * 1.判斷是否已經初始化 若沒有則返回defaultLoggerContext * 2.若已經初始化 則通過contextSelectorBinder 返回loggerContext * @return */ public ILoggerFactory getLoggerFactory() { //如果initialized是false,那么會直接返回defaultLoggerContext if (!initialized) { return defaultLoggerContext; } //否則就委托剛才提到的ContextSelectorStaticBinder返回一個ContextSelector if (contextSelectorBinder.getContextSelector() == null) { throw new IllegalStateException("contextSelector cannot be null. See also " + NULL_CS_URL); } return contextSelectorBinder.getContextSelector().getLoggerContext(); } public String getLoggerFactoryClassStr() { return contextSelectorBinder.getClass().getName(); }
其中init()和getLoggerFactory()為核心方法。
這個初始化方法init()里做了2件事
第一件事是委托ContextInitializer類對defaultLoggerContext進行初始化。這里如果找到了任一配置文件,就會根據配置文件去初始化LoggerContext,如果沒找到,會使用默認配置。關於如何根據配置文件進行配置上下文的,在后面的博客中介紹,這里先略過。
第二件事是對ContextSelectorStaticBinder類進行初始化。
我們再來大致看一下ContextSelectorStaticBinder的代碼:
/** * 餓漢單例 */ static ContextSelectorStaticBinder singleton = new ContextSelectorStaticBinder(); /** * 上下文選擇器 */ ContextSelector contextSelector; /** * 用來做權限控制 */ Object key;
/** * FOR INTERNAL USE. This method is intended for use by StaticLoggerBinder. * * 內部使用,這個方法是給StaticLoggerBinder類來調用的 * 這用權限控制的方式可以用來借鑒 * @param defaultLoggerContext * @throws ClassNotFoundException * @throws NoSuchMethodException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException */ public void init(LoggerContext defaultLoggerContext, Object key) throws ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException { /** * key 用來做權限控制 當key對象初始化后不能變更 * 在StaticLoggerBinder中private static Object KEY = new Object();初始化一個靜態對象key * 則static 只有StaticLoggerBinder類能夠調用該init() */ if (this.key == null) { this.key = key; } else if (this.key != key) { throw new IllegalAccessException("Only certain classes can access this method."); } String contextSelectorStr = OptionHelper.getSystemProperty(ClassicConstants.LOGBACK_CONTEXT_SELECTOR); if (contextSelectorStr == null) { contextSelector = new DefaultContextSelector(defaultLoggerContext); } else if (contextSelectorStr.equals("JNDI")) { // if jndi is specified, let's use the appropriate class contextSelector = new ContextJNDISelector(defaultLoggerContext); } else { contextSelector = dynamicalContextSelector(defaultLoggerContext, contextSelectorStr); } } /** * Instantiate the context selector class designated by the user. The selector * must have a constructor taking a LoggerContext instance as an argument. * * @param defaultLoggerContext * @param contextSelectorStr * @return an instance of the designated context selector class * @throws ClassNotFoundException * @throws SecurityException * @throws NoSuchMethodException * @throws IllegalArgumentException * @throws InstantiationException * @throws IllegalAccessException * @throws InvocationTargetException */ static ContextSelector dynamicalContextSelector(LoggerContext defaultLoggerContext, String contextSelectorStr) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { Class<?> contextSelectorClass = Loader.loadClass(contextSelectorStr); Constructor cons = contextSelectorClass.getConstructor(new Class[] { LoggerContext.class }); return (ContextSelector) cons.newInstance(defaultLoggerContext); } public ContextSelector getContextSelector() { return contextSelector; }
如果系統參數中配置了JNDI,這里會得到一個ContextJNDISelector,實際應用中,一般會得到一個DefaultContextSelector,並且把已經初始化完成的defaultLoggerContext傳給新創建的這個DefaultContextSelector。
總結一下這個大致流程:
1、StaticLoggerBinder委托ContxetInitializer去初始化上下文,這個時候會去讀取配置文件,並根據配置文件對LoggerContext進行初始化
2、然后初始化ContextSelectorStaticBinder,選擇一個合適的ContextSelector並把defaultLoggerContext傳遞給它。
3、調用getLoggerFactory()方法,若未初始化返回defaultLoggerContext,否則委托ContextSelectorStaticBinder獲取一個ContextSelector返回一個上下文。