log4j源码分析:配置文件是如何加载进去的

环境

java: 1.8+
IDEA:2020.3

前言

最近在研究QLExpress表达式时,上面有关这样一个写法:

ExpressRunner runner = new ExpressRunner(false,true);

第二个参数就是是否跟踪执行指令的过程

但是它还要去log级别设置为debug
这次我就好奇,该如何设置呢?
参考官方源码,其实就是引入了log4j.properties文件。然后根据包路径设置debug

流程图

在这里插入图片描述

简单源码分析

当我们在类中写入:

private static final Log log = LogFactory.getLog(ExpressRunner.class);

org.apache.commons.logging.LogFactory 类被使用时,就会开始对Log进行加载初始化。
而由于它只有一个实现类:org.apache.commons.logging.impl.LogFactoryImpl,所以后面的代码其实就是LogFactoryImpl的。(LogFactory只是一个抽象类)

public static Log getLog(Class clazz)
    throws LogConfigurationException {
    return (getFactory().getInstance(clazz));
}

因为只想知道配置文件时如何加载并初始化的,所以不分析getFactory()这里的内容,只分析getInstance()方法;

一路向下走,来到:

public Log getInstance(String name) throws LogConfigurationException {
    Log instance = (Log) instances.get(name);
    if (instance == null) {
        instance = newInstance(name);
        instances.put(name, instance);
    }
    return (instance);
}

接着来到了newInstance()方法:

protected Log newInstance(String name) throws LogConfigurationException {
    Log instance = null;
    try {
        //第一次的话,logConstructor肯定为null
        if (logConstructor == null) {
            instance = discoverLogImplementation(name);
        }
        else {
            Object params[] = { name };
            instance = (Log) logConstructor.newInstance(params);
        }
        
        if (logMethod != null) {
            Object params[] = { this };
            logMethod.invoke(instance, params);
        }
        return (instance);
    } catch (LogConfigurationException lce) {
        // this type of exception means there was a problem in discovery
        // and we've already output diagnostics about the issue, etc.; 
        // just pass it on
        throw (LogConfigurationException) lce;
    } catch (InvocationTargetException e) {
        // A problem occurred invoking the Constructor or Method 
        // previously discovered
        Throwable c = e.getTargetException();
        if (c != null) {
            throw new LogConfigurationException(c);
        } else {
            throw new LogConfigurationException(e);
        }
    } catch (Throwable t) {
        // A problem occurred invoking the Constructor or Method 
        // previously discovered
        throw new LogConfigurationException(t);
    }
}

上面的代码中discoverLogImplementation()方法是关键:
而这个方法中关键的代码(只贴关键代码):

...
private static final String[] classesToDiscover = {
        LOGGING_IMPL_LOG4J_LOGGER,
        "org.apache.commons.logging.impl.Jdk14Logger",
        "org.apache.commons.logging.impl.Jdk13LumberjackLogger",
        "org.apache.commons.logging.impl.SimpleLog"
};
...
for(int i=0; (i<classesToDiscover.length) && (result == null); ++i) {
    result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
...

接着就来到了createLogFromClass()方法,它的关键代码:

...
//logAdapterClassName就是上面静态数组里存的全类名
Class c = null;
try {
//根据全类名将类加载进来
    c = Class.forName(logAdapterClassName, true, currentCL);
} catch (ClassNotFoundException originalClassNotFoundException) {
    String msg = "" + originalClassNotFoundException.getMessage();
    logDiagnostic(
        "The log adapter '"
        + logAdapterClassName
        + "' is not available via classloader " 
        + objectId(currentCL)
        + ": "
        + msg.trim());
    try {
        c = Class.forName(logAdapterClassName);
    } catch (ClassNotFoundException secondaryClassNotFoundException) {
        msg = "" + secondaryClassNotFoundException.getMessage();
        logDiagnostic(
            "The log adapter '"
            + logAdapterClassName
            + "' is not available via the LogFactoryImpl class classloader: "
            + msg.trim());
        break;
    }
}
//这里是关键
//利用反射,调用相应类的构造方法
 constructor = c.getConstructor(logConstructorSignature);
 Object o = constructor.newInstance(params);

当利用反射调用到Log4JLogger构造方法时:

public Log4JLogger(String name) {
    this.name = name;
    //关键
    this.logger = getLogger();
}

public Logger getLogger() {
    if (logger == null) {
        logger = Logger.getLogger(name);
    }
    return (this.logger);
}

当执行到Logger.getLogger(name)时:

Logger getLogger(String name) {
  return LogManager.getLogger(name);//这里调用的是静态方法
}

在调用上面静态方法时,会先加载LogManager类,加载类时,又会执行里面静态代码块:
只贴关键代码:

static {
   // By default we use a DefaultRepositorySelector which always returns 'h'.
   Hierarchy h = new Hierarchy(new RootLogger((Level) Level.DEBUG));
   repositorySelector = new DefaultRepositorySelector(h);

	//
   String override =OptionConverter.getSystemProperty(DEFAULT_INIT_OVERRIDE_KEY,
					       null);

   // if there is no default init override, then get the resource
   // specified by the user or the default config file.
   if(override == null || "false".equalsIgnoreCase(override)) {

     String configurationOptionStr = OptionConverter.getSystemProperty(
						  DEFAULT_CONFIGURATION_KEY, 
						  null);

     String configuratorClassName = OptionConverter.getSystemProperty(
                                                  CONFIGURATOR_CLASS_KEY, 
					   null);

     URL url = null;

     // if the user has not specified the log4j.configuration
     // property, we search first for the file "log4j.xml" and then
     // "log4j.properties"
     if(configurationOptionStr == null) {
     	//DEFAULT_XML_CONFIGURATION_FILE:log4j.xml	
		url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
		if(url == null) {
		 //DEFAULT_CONFIGURATION_FILE:log4j.properties
	     url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
		}
     } else {
try {
  url = new URL(configurationOptionStr);
} catch (MalformedURLException ex) {
  // so, resource is not a URL:
  // attempt to get the resource from the class path
  url = Loader.getResource(configurationOptionStr); 
}	
     }
     
     // If we have a non-null url, then delegate the rest of the
     // configuration to the OptionConverter.selectAndConfigure
     // method.
     if(url != null) {
    LogLog.debug("Using URL ["+url+"] for automatic log4j configuration.");
       try {
           OptionConverter.selectAndConfigure(url, configuratorClassName,
				   LogManager.getLoggerRepository());
       } catch (NoClassDefFoundError e) {
           LogLog.warn("Error during default initialization", e);
       }
     } else {
    LogLog.debug("Could not find resource: ["+configurationOptionStr+"].");
     }
   } else {
       LogLog.debug("Default initialization of overridden by " + 
           DEFAULT_INIT_OVERRIDE_KEY + "property."); 
   }  
 }

终于到了最关键的代码了,上面代码核心就是下面:

if(configurationOptionStr == null) {
   	//DEFAULT_XML_CONFIGURATION_FILE:log4j.xml	
	url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE);
	if(url == null) {
	 //DEFAULT_CONFIGURATION_FILE:log4j.properties
	 //这里就会去加载
	    url = Loader.getResource(DEFAULT_CONFIGURATION_FILE);
	}
}

后面的代码就是将配置信息里的东西读取出来,并创建logger对象来记录下来,而logger对象会被缓存中一个Hashtable ht;

Logger getLogger(final String name) {
   // Delegate the actual manufacturing of the logger to the logger repository.
  return getLoggerRepository().getLogger(name);
}
public Logger getLogger(String name, LoggerFactory factory) {
    //System.out.println("getInstance("+name+") called.");
    CategoryKey key = new CategoryKey(name);
    // Synchronize to prevent write conflicts. Read conflicts (in
    // getChainedLevel method) are possible only if variable
    // assignments are non-atomic.
    Logger logger;
	//ht就是hashtable
    synchronized(ht) {
      Object o = ht.get(key);
      if(o == null) {
		logger = factory.makeNewLoggerInstance(name);
		logger.setHierarchy(this);
		ht.put(key, logger);
		updateParents(logger);
		return logger;
      } else if(o instanceof Logger) {
		return (Logger) o;
      } else if (o instanceof ProvisionNode) {
		//System.out.println("("+name+") ht.get(this) returned ProvisionNode");
		logger = factory.makeNewLoggerInstance(name);
		logger.setHierarchy(this);
		ht.put(key, logger);
		updateChildren((ProvisionNode) o, logger);
		updateParents(logger);
		return logger;
      }
      else {
		// It should be impossible to arrive here
		return null;  // but let's keep the compiler happy.
      }
    }
  }

自此就分析完毕了。

总结

关键核心就是代码某处使用Loader.getResource()来进行资源的加载。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

山鬼谣me

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值