logback源碼解析之配置檔案解析
這個代碼就是真正實作方法,那麼咱們看一下該方法
logback-classic 中實作的//org.slf4j.impl.StaticLoggerBinder
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
這裡直接傳回了一個SINGLETON,那麼這個SINGLETON是從哪裡來的,通過檢視它的定義,定義如下
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
是通過靜态常量做的單利模式,在檢視這個類裡,發現這個類裡有一個靜态代碼塊
logback初始化配置
static {
SINGLETON.init();
}
這裡Logback做了一些初始化代碼,咱們檢視一下都初始化了什麼
void init() {
try {
try {
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);
}
contextSelectorBinder.init(defaultLoggerContext, KEY);
initialized = true;
} catch (Exception t) { // see LOGBACK-1159
Util.report("Failed to instantiate [" + LoggerContext.class.getName() + "]", t);
}
}
初始化上下文,并加載自動配置,那麼先看一下上下文都初始化了什麼new ContextInitializer(defaultLoggerContext),這裡有一個defaultLoggerContext,
那麼這個變量具體是什麼值,咱們來看一下,剛看到這裡的時候還有些小疑惑,怎麼defaultLoggerContext是一個普通的成員變量,應該是靜态代碼塊優先執行啊,
後來發現SINGLETON變量是靜态變量,那麼這個靜态變量在static靜态代碼塊前面,他優先執行,那麼這個順序就是SINGLETON->defaultLoggerContext->StaticLoggerBinder()->SINGLETON.init,
先看一下defaultLoggerContext的聲明
private LoggerContext defaultLoggerContext = new LoggerContext();
按順尋來看一下StaticLoggerBinder()方法中做了什麼
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
簡單設定了預設上下文名稱,那麼這個名稱又是什麼呢?,通過追蹤定義,在logbakc-core子產品中發現定義的核心常量
public static final String DEFAULT_CONTEXT_NAME = "default";
原來隻是把預設上下文的名稱設定為default
new ContextInitializer(defaultLoggerContext),到此我們知道了,預設上下文是在這個地方設定的,
ch.qos.logback.classic.util.ContextInitializer
public ContextInitializer(LoggerContext loggerContext) {
this.loggerContext = loggerContext;
}
然後我們再來看一下,這個new ContextInitializer(defaultLoggerContext).autoConfig();
autoConfig()方法都做了什麼
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
logback的預設配置,首先做了狀态艦艇配置設定為預設的loggerContext上下文,看一下這裡的設定,為何這麼做
這個設定是在logback-core中操作的,
ch.qos.logback.core.util.StatusListenerConfigHelper
public static void installIfAsked(Context context) {
String slClass = OptionHelper.getSystemProperty(CoreConstants.STATUS_LISTENER_CLASS_KEY);
if (!OptionHelper.isEmpty(slClass)) {
addStatusListener(context, slClass);
}
}
這個代碼中擷取系統屬性,有另一個常量,需要看一下具體是什麼
java
final public static String STATUS_LISTENER_CLASS_KEY = "logback.statusListenerClass";
Ok ,知道變量内容,然後我們接着看,OptionHelper.getSystemProperty的方法
ch.qos.logback.core.OptionHelper
public static String getSystemProperty(String key) {
try {
return System.getProperty(key);
} catch (SecurityException e) {
return null;
}
}
嗯知識擷取監聽類,如果有的話,把它加入狀态監聽,
private static void addStatusListener(Context context, String listenerClassName) {
StatusListener listener = null;
if (CoreConstants.SYSOUT.equalsIgnoreCase(listenerClassName)) {
listener = new OnConsoleStatusListener();
} else {
listener = createListenerPerClassName(context, listenerClassName);
}
initAndAddListener(context, listener);
}
logback-core 中的 CoreConstants.SYSOUT常量
final public static String SYSOUT = "SYSOUT";
如果與監聽類相同,那麼建立控制台狀态監聽,否則createListenerPerClassName(context,listenerClassName)為指定的上下文,建立指定的監聽類,咱們看一下是如何建立監聽類的,方法如下
logback-core 中的 ch.qos.logback.core.util.StatusListenerConfigHelper
private static StatusListener createListenerPerClassName(Context context, String listenerClass) {
try {
return (StatusListener) OptionHelper.instantiateByClassName(listenerClass, StatusListener.class, context);
} catch (Exception e) {
// printing on the console is the best we can do
e.printStackTrace();
return null;
}
}
根據類名建立監聽類,估計是用了反射之類的技術,那麼咱們就深入方法,一探究竟
logback-core 中的 ch.qos.logback.core.util.OptionHelper
public static Object instantiateByClassName(String className, Class<?> superClass, Context context) throws IncompatibleClassException,
DynamicClassLoadingException {
ClassLoader classLoader = Loader.getClassLoaderOfObject(context);
return instantiateByClassName(className, superClass, classLoader);
}
方法是很簡單,通過方法名能看出是根據對象找到對應的類加載器,但是這塊代碼做了封裝,咱們接着看Loader.getClassLoaderOfObject()方法,看他裡面是如何實作的
logback-core中的ch.qos.logback.core.util.Loader
public static ClassLoader getClassLoaderOfObject(Object o) {
if (o == null) {
throw new NullPointerException("Argument cannot be null");
}
return getClassLoaderOfClass(o.getClass());
}
通過方法,可以看到裡面又調用了,getClassLoaderOfClass(clazz)傳遞的參數是一個clazz,接着看
public static ClassLoader getClassLoaderOfClass(final Class<?> clazz) {
ClassLoader cl = clazz.getClassLoader();
if (cl == null) {
return ClassLoader.getSystemClassLoader();
} else {
return cl;
}
}
嗯,看到這裡,依舊是,根據clazz擷取對象的類加載器,如果沒有的話,就用系統的類加載器,OK,得到類加載器之後,調用了instantiateByClassName(clazz,clazz,classLoader)的重載方法,看這個方法,裡面是又如何處理的
logback-core 中的 ch.qos.logback.core.util.OptionHelper
public static Object instantiateByClassName(String className, Class<?> superClass, ClassLoader classLoader) throws IncompatibleClassException,
DynamicClassLoadingException {
return instantiateByClassNameAndParameter(className, superClass, classLoader, null, null);
}
這裡有調用了,通過類名和參數初始化類的方法instantiateByClassNameAndParameter()
public static Object instantiateByClassNameAndParameter(String className, Class<?> superClass, ClassLoader classLoader, Class<?> type, Object parameter)
throws IncompatibleClassException, DynamicClassLoadingException {
if (className == null) {
throw new NullPointerException();
}
try {
Class<?> classObj = null;
classObj = classLoader.loadClass(className);
if (!superClass.isAssignableFrom(classObj)) {
throw new IncompatibleClassException(superClass, classObj);
}
if (type == null) {
return classObj.newInstance();
} else {
Constructor<?> constructor = classObj.getConstructor(type);
return constructor.newInstance(parameter);
}
} catch (IncompatibleClassException ice) {
throw ice;
} catch (Throwable t) {
throw new DynamicClassLoadingException("Failed to instantiate type " + className, t);
}
}
方法,很簡單,首先通過類加載器加載類,然後,判斷加載的類與父類是否是繼承關系,由于,類型為空那麼到這裡就是直接執行個體化了OK,到這裡,上下文監聽,
接下來該初始化和添加上下文的監聽了,
logback-core中的 ch.qos.logback.core.util.StatusListenerConfigHelper
private static void initAndAddListener(Context context, StatusListener listener) {
if (listener != null) {
if (listener instanceof ContextAware) // LOGBACK-767
((ContextAware) listener).setContext(context);
boolean effectivelyAdded = context.getStatusManager().add(listener);
if (effectivelyAdded && (listener instanceof LifeCycle)) {
((LifeCycle) listener).start(); // LOGBACK-767
}
}
}
首先給監聽設定上下文,然後,擷取狀态管理,并把監聽添加進去,咱們先看一下擷取狀态監聽的方法,由于getStatusManager是一個接口方法,我們在Context中看到,那麼咱們看一下基礎實作,然後在ContextBase實作類中找到如下的代碼
logback-core中的ch.qos.logback.core.ContextBase
public StatusManager getStatusManager() {
return sm;
}
直接傳回了sm,那麼sm是什麼呢?通過查找定義,發現如下代碼
private StatusManager sm = new BasicStatusManager();
Ok,那麼咱們就看一下基礎狀态管理是如何處理添加狀态監聽事件的找到如下方法
logback-core中的ch.qos.logback.core.BasicStatusManager
public boolean add(StatusListener listener) {
synchronized (statusListenerListLock) {
if (listener instanceof OnConsoleStatusListener) {
boolean alreadyPresent = checkForPresence(statusListenerList, listener.getClass());
if (alreadyPresent)
return false;
}
statusListenerList.add(listener);
}
return true;
}
代碼中通過鎖定狀态監聽集合對象 這個對象的作用是幹什麼呢,
final protected LogbackLock statusListenerListLock = new LogbackLock();
logbacklock 隻是為了在分析線程時更容易識别的一個标志,本身無意義,但是能夠讓程式出問題是更容易分析
public class LogbackLock extends Object {
}
然後檢查監聽是否已經存在,如果存在,直接傳回false,看一下如何檢查是否存在的
private boolean checkForPresence(List<StatusListener> statusListenerList, Class<?> aClass) {
for (StatusListener e : statusListenerList) {
if (e.getClass() == aClass)
return true;
}
return false;
}
``
看完後發現是通過判斷clazz,并沒有利用list集合自帶的方法來處理這個問題?這裡需要考慮下為什麼?不用集合自帶的方法,是因為什麼呢?
如果不存在的話,就直接通過集合的添加方法加進去了,嗯就是一個普通的集合,不過限制為隻有子類才能通路,
```java
final protected List<StatusListener> statusListenerList = new ArrayList<StatusListener>();
<div class="se-preview-section-delimiter"></div>
接着看後面的方法,隻有當添加成功,并且實作類LifeCycle的類會啟動他,到這裡,上下文監聽,的内容已經徹底完結,通過start方法已經開始了運作,
看了多這麼多的代碼,我們接着回到我們autoConfig方法中,
第二行代碼findURLOfDefaultConfigurationFile查找預設的配置檔案
logback-classic中的ch.qos.logback.classic.util.ContextInitializer
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
<div class="se-preview-section-delimiter"></div>
看看預設的配置檔案是如何處理的,首先,第一行代碼擷取了家在目前類的類加載器,然後從系統屬性中查找配置檔案findConfigFileURLFromSystemProperties()
如何查找呢?
logback-classic中的ch.qos.logback.classic.util.ContextInitializer
private URL findConfigFileURLFromSystemProperties(ClassLoader classLoader, boolean updateStatus) {
String logbackConfigFile = OptionHelper.getSystemProperty(CONFIG_FILE_PROPERTY);
if (logbackConfigFile != null) {
URL result = null;
try {
result = new URL(logbackConfigFile);
return result;
} catch (MalformedURLException e) {
// so, resource is not a URL:
// attempt to get the resource from the class path
result = Loader.getResource(logbackConfigFile, classLoader);
if (result != null) {
return result;
}
File f = new File(logbackConfigFile);
if (f.exists() && f.isFile()) {
try {
result = f.toURI().toURL();
return result;
} catch (MalformedURLException e1) {
}
}
} finally {
if (updateStatus) {
statusOnResourceSearch(logbackConfigFile, classLoader, result);
}
}
}
return null;
}
<div class="se-preview-section-delimiter"></div>
這裡有一個CONFIG_FILE_PROPERTY屬性,通過查找定義
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
<div class="se-preview-section-delimiter"></div>
很簡單,通過擷取設定配置檔案路徑,然後判斷設定的屬性,如果位URL則直接執行個體化URL,并傳回,如果不是,則檢視是否是一個類資源配置檔案,如果是直接傳回對應的URL,如果不是,再檢視是否是一個普通的檔案路徑,如果是則執行個體化檔案對象,然後轉換為URI,然後把URI轉換為URL傳回
由于,咱們沒有配置這個參數,那麼,直接往下面看代碼 TEST_AUTOCONFIG_FILE ,看到這麼個變量
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
<div class="se-preview-section-delimiter"></div>
如果有測試的配置檔案,優先使用測試配置檔案,如果有 GROOVY_AUTOCONFIG_FILE ,
final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
<div class="se-preview-section-delimiter"></div>
如果以上都沒有,那麼最後的預設配置
final public static String AUTOCONFIG_FILE = "logback.xml";
<div class="se-preview-section-delimiter"></div>
嗯,這裡的等于說傳回了配置檔案所在的位置,如果有配置檔案,
那麼使用配置檔案configureByResource()
public void configureByResource(URL url) throws JoranException {
if (url == null) {
throw new IllegalArgumentException("URL argument cannot be null");
}
final String urlString = url.toString();
if (urlString.endsWith("groovy")) {
if (EnvUtil.isGroovyAvailable()) {
// avoid directly referring to GafferConfigurator so as to avoid
// loading groovy.lang.GroovyObject . See also http://jira.qos.ch/browse/LBCLASSIC-214
GafferUtil.runGafferConfiguratorOn(loggerContext, this, url);
} else {
StatusManager sm = loggerContext.getStatusManager();
sm.add(new ErrorStatus("Groovy classes are not available on the class path. ABORTING INITIALIZATION.", loggerContext));
}
} else if (urlString.endsWith("xml")) {
JoranConfigurator configurator = new JoranConfigurator();
configurator.setContext(loggerContext);
configurator.doConfigure(url);
} else {
throw new LogbackException("Unexpected filename extension of file [" + url.toString() + "]. Should be either .groovy or .xml");
}
}
<div class="se-preview-section-delimiter"></div>
首先判斷是groovy還是xml的,如果是groovy,那麼先判斷Groovy的環境是否可用,檢視一下是如何判斷的
static public boolean isGroovyAvailable() {
ClassLoader classLoader = Loader.getClassLoaderOfClass(EnvUtil.class);
try {
Class<?> bindingClass = classLoader.loadClass("groovy.lang.Binding");
return (bindingClass != null);
} catch (ClassNotFoundException e) {
return false;
}
}
<div class="se-preview-section-delimiter"></div>
首先擷取EnvUtil的類加載器,然後加載groovy.langBinding,如果不為空,則傳回true,嗯,這裡先不看groovy
先來檢視xml的,首先初始化了JoranConfigurator,然後設定了,日志上下文,然後加載配置,咱們來看一下是如何加載配置,如何解析配置檔案的
進入JoranConfigurator中并沒有找到doConfigure,然後在他父類JoranConfiguratorBase的父類GenericConfigurator中找到了
logback-core 中的 ch.qos.logback.core.joran.GenericConfigurator
public final void doConfigure(URL url) throws JoranException {
InputStream in = null;
try {
informContextOfURLUsedForConfiguration(getContext(), url);
URLConnection urlConnection = url.openConnection();
// per http://jira.qos.ch/browse/LBCORE-105
// per http://jira.qos.ch/browse/LBCORE-127
urlConnection.setUseCaches(false);
in = urlConnection.getInputStream();
doConfigure(in, url.toExternalForm());
} catch (IOException ioe) {
String errMsg = "Could not open URL [" + url + "].";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ioe) {
String errMsg = "Could not close input stream";
addError(errMsg, ioe);
throw new JoranException(errMsg, ioe);
}
}
}
}
<div class="se-preview-section-delimiter"></div>
先看第一行代碼,通知使用者上下文的配置
logback-core 中的 ch.qos.logback.core.joran.GenericConfigurator
public static void informContextOfURLUsedForConfiguration(Context context, URL url) {
ConfigurationWatchListUtil.setMainWatchURL(context, url);
}
<div class="se-preview-section-delimiter"></div>
嗯,先不看通知的事,咱們先看一下,把配置檔案解析為inputStrem和資源的外部來源後,要如何操作
logback-core 中的 ch.qos.logback.core.joran.GenericConfigurator
public final void doConfigure(InputStream inputStream, String systemId) throws JoranException {
InputSource inputSource = new InputSource(inputStream);
inputSource.setSystemId(systemId);
doConfigure(inputSource);
}
<div class="se-preview-section-delimiter"></div>
然後根據inputstream建立了xml.sax解析的inputsource的對象,并把配置檔案的外部來源設定為系統id,然後又調用了docConfigure方法
logback-core 中的 ch.qos.logback.core.joran.GenericConfigurator
public final void doConfigure(final InputSource inputSource) throws JoranException {
long threshold = System.currentTimeMillis();
// if (!ConfigurationWatchListUtil.wasConfigurationWatchListReset(context)) {
// informContextOfURLUsedForConfiguration(getContext(), null);
// }
SaxEventRecorder recorder = new SaxEventRecorder(context);
recorder.recordEvents(inputSource);
doConfigure(recorder.saxEventList);
// no exceptions a this level
StatusUtil statusUtil = new StatusUtil(context);
if (statusUtil.noXMLParsingErrorsOccurred(threshold)) {
addInfo("Registering current configuration as safe fallback point");
registerSafeConfiguration(recorder.saxEventList);
}
}
<div class="se-preview-section-delimiter"></div>
首先擷取目前時間的毫秒數,然後建立了Sax事件記錄,然後吧配置檔案的sax inputsource設定,解析配置檔案集合
那麼咱們先看,SaxEventRecorder中做了什麼
logback-core 中的ch.qos.logback.core.joran.event.SaxEventRecorder
public SaxEventRecorder(Context context) {
cai = new ContextAwareImpl(context, this);
}
<div class="se-preview-section-delimiter"></div>
嗯,其中建立了一個上下文感應實作,然後看一下recordEvents()中做了什麼
public List<SaxEvent> recordEvents(InputSource inputSource) throws JoranException {
SAXParser saxParser = buildSaxParser();
try {
saxParser.parse(inputSource, this);
return saxEventList;
} catch (IOException ie) {
handleError("I/O error occurred while parsing xml file", ie);
} catch (SAXException se) {
// Exception added into StatusManager via Sax error handling. No need to add it again
throw new JoranException("Problem parsing XML document. See previously reported errors.", se);
} catch (Exception ex) {
handleError("Unexpected exception while parsing XML document.", ex);
}
throw new IllegalStateException("This point can never be reached");
}
<div class="se-preview-section-delimiter"></div>
記錄事件這個方法中做了什麼呢,第一步先建構了SaxParser,
private SAXParser buildSaxParser() throws JoranException {
try {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setValidating(false);
spf.setNamespaceAware(true);
return spf.newSAXParser();
} catch (Exception pce) {
String errMsg = "Parser configuration error occurred";
addError(errMsg, pce);
throw new JoranException(errMsg, pce);
}
}
<div class="se-preview-section-delimiter"></div>
嗯建構SaxParser,是通過SAXParserFactory工廠類執行個體的,,并且不設定驗證,感覺命名為空,然後通過工廠建立SaxParser,建立完SAXParser後就開始轉換了
public void parse(InputSource is, DefaultHandler dh)
throws SAXException, IOException {
if (is == null) {
throw new IllegalArgumentException("InputSource cannot be null");
}
XMLReader reader = this.getXMLReader();
if (dh != null) {
reader.setContentHandler(dh);
reader.setEntityResolver(dh);
reader.setErrorHandler(dh);
reader.setDTDHandler(dh);
}
reader.parse(is);
}
<div class="se-preview-section-delimiter"></div>
解析xml需要一個輸入源和預設的處理類,這裡建立了一個XMLReader,然後reader.parse
<div class="se-preview-section-delimiter"></div>
嗯,上面利用SAX解析了xml後,把解析的内容都放到SAXEvent中,接着看doConfigure()
public void doConfigure(final List<SaxEvent> eventList) throws JoranException {
buildInterpreter();
// disallow simultaneous configurations of the same context
synchronized (context.getConfigurationLock()) {
interpreter.getEventPlayer().play(eventList);
}
}
<div class="se-preview-section-delimiter"></div>
這裡面第一個建構攔截器,然後為擷取配置檔案加鎖,在攔截其中處理XML内容的具體解析,先來看一下buildInterpreter()方法中,做了什麼,如何建構攔截器,這個攔截器的作用
protected void buildInterpreter() {
RuleStore rs = new SimpleRuleStore(context);
addInstanceRules(rs);
this.interpreter = new Interpreter(context, rs, initialElementPath());
InterpretationContext interpretationContext = interpreter.getInterpretationContext();
interpretationContext.setContext(context);
addImplicitRules(interpreter);
addDefaultNestedComponentRegistryRules(interpretationContext.getDefaultNestedComponentRegistry());
}
<div class="se-preview-section-delimiter"></div>
首先建構了規則庫,建立了簡單的規則庫,并傳遞了參數,然後增加攔截器規則,建立攔截器,擷取攔截器上下文,添加
首先來看一下規則庫,
<div class="se-preview-section-delimiter"></div>
配置檔案解析呢就是通過SAX解析的,那麼咱們來看一下沒有制定配置檔案時,預設的配置是如何處理的
還是這個初始化類,ch.qos.logback.classic.util.ContextInitializer#autoConfig
public void autoConfig() throws JoranException {
StatusListenerConfigHelper.installIfAsked(loggerContext);
URL url = findURLOfDefaultConfigurationFile(true);
if (url != null) {
configureByResource(url);
} else {
///2222
Configurator c = EnvUtil.loadFromServiceLoader(Configurator.class);
if (c != null) {
try {
c.setContext(loggerContext);
c.configure(loggerContext);
} catch (Exception e) {
throw new LogbackException(String.format("Failed to initialize Configurator: %s using ServiceLoader", c != null ? c.getClass()
.getCanonicalName() : "null"), e);
}
} else {
BasicConfigurator basicConfigurator = new BasicConfigurator();
basicConfigurator.setContext(loggerContext);
basicConfigurator.configure(loggerContext);
}
}
}
<div class="se-preview-section-delimiter"></div>
我們來看一下這個第二部分的内容,從ServiceLoader中加載配置檔案,如果加載不到的話,就初始化一個基礎的配置檔案,咱們先來看一下,這個從服務加載器中加載配置
ch.qos.logback.classic.util.EnvUtil#loadFromServiceLoader
public static <T> T loadFromServiceLoader(Class<T> c) {
ServiceLoader<T> loader = ServiceLoader.load(c, getServiceLoaderClassLoader());
Iterator<T> it = loader.iterator();
if (it.hasNext())
return it.next();
return null;
}
<div class="se-preview-section-delimiter"></div>
擷取ServiceLoader的classLoader,搞明白這個serviceloader是什麼類加載器?
ch.qos.logback.classic.util.EnvUtil#getServiceLoaderClassLoader
private static ClassLoader getServiceLoaderClassLoader() {
return testServiceLoaderClassLoader == null ? Loader.getClassLoaderOfClass(EnvUtil.class) : testServiceLoaderClassLoader;
}
<div class="se-preview-section-delimiter"></div>
到這裡就很明白了,如果是有測試服務類加載器就用測試的,如果沒有的話, 就用EnvUtil的類加載器來加載配置檔案,那麼ServiceLoader的作用是什麼呢?
SeriveLoader的作用是根據一個接口擷取其所有子類實作,如果有的話取第一個實作,看一下基礎的配置,都有什麼
public class BasicConfigurator extends ContextAwareBase implements Configurator {
public BasicConfigurator() {
}
public void configure(LoggerContext lc) {
addInfo("Setting up default configuration.");
ConsoleAppender<ILoggingEvent> ca = new ConsoleAppender<ILoggingEvent>();
ca.setContext(lc);
ca.setName("console");
LayoutWrappingEncoder<ILoggingEvent> encoder = new LayoutWrappingEncoder<ILoggingEvent>();
encoder.setContext(lc);
// same as
// PatternLayout layout = new PatternLayout();
// layout.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
TTLLLayout layout = new TTLLLayout();
layout.setContext(lc);
layout.start();
encoder.setLayout(layout);
ca.setEncoder(encoder);
ca.start();
Logger rootLogger = lc.getLogger(Logger.ROOT_LOGGER_NAME);
rootLogger.addAppender(ca);
}
}
最簡單的日至配置,有一個控制台日志列印, 至此所有的配置檔案處理到此結束