log4j2原始碼解析(2)--LoggerContext
LoggerContext作用及初始化流程
根據我們在Log4j初識中的例項可以看出,在不適用日誌門面外掛slf4j的情況下,獲取logger的方式一般為
Logger logger = logManager.getLogger(xx.class)
可以看到,通常情況下,我們會使用LogManager的靜態方法getLogger方法獲取logger,下面我們就簡要看一下這個方法的流程。
public static Logger getLogger(final Class<?> clazz) { final Class<?> cls = callerClass(clazz); return getContext(cls.getClassLoader(), false).getLogger(toLoggerName(cls)); }
這個方法返回了一個鏈式呼叫後的結果,而首先是先呼叫了getContext,這個方法返回的是一個LoggerContext物件,該方法定義如下:
public static LoggerContext getContext(final ClassLoader loader, final boolean currentContext) { try { return factory.getContext(FQCN, loader, null, currentContext); } catch (final IllegalStateException ex) { LOGGER.warn(ex.getMessage() + " Using SimpleLogger"); return new SimpleLoggerContextFactory().getContext(FQCN, loader, null,currentContext); } }
這個方法中的LoggerContext是通過LoggerFactory的getContext方法,預設情況下,對應的子類是Log4jLoggerFactory。對應的方法如下:
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext, final boolean currentContext) { //獲得LoggerContex例項 final LoggerContext ctx = selector.getContext(fqcn, loader, currentContext); if (externalContext != null && ctx.getExternalContext() == null) { ctx.setExternalContext(externalContext); } //啟動LoggerContext if (ctx.getState() == LifeCycle.State.INITIALIZED) { ctx.start(); } return ctx; }
其中裡面的主要邏輯是返回一個啟動的LoggerContext例項,其中LoggerContext例項是通過Log4jLoggerFactory中的ContextSelector獲取的,預設情況下使用的是ClassLoaderContextSelector,在Log4jLoggerFactory的構造方法中進行初始化。
完成初始化後,就是LoggerContext的啟動,如果當前的context例項的狀態為初始化狀態,則呼叫start函式,其中的流程是
LogContext.start()—>LogContext.reconfigure()—>LogContext.reconfigure(URI)
其中主要做的事情就是根據配置檔案的位置,讀取相應的log4j2配置檔案,解析配置檔案,最終解析為各種Appender以及Logger的Java物件,下面我們來看大致的流程
首先會獲取ConfigurationFactory的例項,然後獲取Configuration,分別看這兩步
1、獲取ConfigurationFactory例項
public static ConfigurationFactory getInstance() { // volatile works in Java 1.6+, so double-checked locking also works properly //noinspection DoubleCheckedLocking if (factories == null) { LOCK.lock(); try { if (factories == null) { final List<ConfigurationFactory> list = new ArrayList<>(); final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY); if (factoryClass != null) { addFactory(list, factoryClass); } final PluginManager manager = new PluginManager(CATEGORY); manager.collectPlugins(); final Map<String, PluginType<?>> plugins = manager.getPlugins(); final List<Class<? extends ConfigurationFactory>> ordered = new ArrayList<>(plugins.size()); for (final PluginType<?> type : plugins.values()) { try { ordered.add(type.getPluginClass().asSubclass(ConfigurationFactory.class)); } catch (final Exception ex) { LOGGER.warn("Unable to add class {}", type.getPluginClass(), ex); } } Collections.sort(ordered, OrderComparator.getInstance()); for (final Class<? extends ConfigurationFactory> clazz : ordered) { addFactory(list, clazz); } // see above comments about double-checked locking //noinspection NonThreadSafeLazyInitialization factories = Collections.unmodifiableList(list); } } finally { LOCK.unlock(); } } LOGGER.debug("Using configurationFactory {}", configFactory); return configFactory;
這是一個典型的單例模式,使用了雙重檢查鎖的方式獲取例項,這個方法裡面有一個操作步驟我們要重點介紹,就是PluginManager例項的建立,PluginManager主要用於管理Log4J中的外掛,這些Plugin主要是一些對應不同格式的配置檔案的解析器,PluginManager中使用category對不同型別的外掛進行快取。
獲取到ConfigurationFactory的例項之後,就是獲取Configuration的過程,主要流程是根據配置的格式,從遍歷上面提到列表中的factory,載入到對應的ConfigurationFactory,隨後通過這個Factory去解析配置檔案最終獲取到Configuration。程式碼流程如下:
public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation, final ClassLoader loader) { if (!isActive()) { return null; } if (loader == null) { return getConfiguration(loggerContext, name, configLocation); } if (isClassLoaderUri(configLocation)) { final String path = extractClassLoaderUriPath(configLocation); final ConfigurationSource source = ConfigurationSource.fromResource(path,loader); if (source != null) { final Configuration configuration = getConfiguration(loggerContext,source); if (configuration != null) { return configuration; } } } return getConfiguration(loggerContext, name, configLocation); }
注意這裡的getConfiguration(loggerContext,source);是一個多型方法,會根據不同型別的配置發文件,呼叫相應的ConfigureFactory的方法,將配置檔案中的tag變為java物件。下一篇文章中,我會以xml型別的檔案詳細介紹將tag變化為Java物件的過程。
謝謝你請我吃糖果