上回我们已经对LogManager类的静态程序块进行了部分解读,现在再次将源码贴上,如下是LogManager类静态程序块全部源码+注释
static {
// Check debug
// 1,从运行环境获取log4j.debug的配置值
String debugProp = System.getProperty("log4j.debug");
//2,对配置值进行判断
if(Boolean.valueOf(debugProp).booleanValue()) {
debug = true;
}
//3,如果debug为true,那么进行输出
if(debug) {
System.out.println("**Start of LogManager static initializer");
}
//4,构建LoggerRespository接口的实现类实例,并将其赋值到对应的属性上
Hierarchy hierarchy = new Hierarchy(new RootLogger(Level.DEBUG));
defaultLoggerRepository = hierarchy;
hierarchy.setName(Constants.DEFAULT_REPOSITORY_NAME);
//5,构建RepositorySelector接口的实现类实例,并将其赋值到对应的属性上
// temporary repository
repositorySelector = new DefaultRepositorySelector(defaultLoggerRepository);
// Attempt to perform automatic configuration of the default repository
//6,从运行环境获取log4j.configuratorClass配置值
String configuratorClassName =
OptionConverter.getSystemProperty(Constants.CONFIGURATOR_CLASS_KEY, null);
//7,从运行环境获取og4j.configuration属性值
String configurationOptionStr =
OptionConverter.getSystemProperty(Constants.DEFAULT_CONFIGURATION_KEY, null);
//如果og4j.configuration属性值为null
if (configurationOptionStr == null) {
//获取log4j.xml的文件路径Url,并判断是否存在,如果不存在,那么就获取log4j.properties
if (Loader.getResource(Constants.DEFAULT_XML_CONFIGURATION_FILE) != null) {
configurationOptionStr = Constants.DEFAULT_XML_CONFIGURATION_FILE;
} else if (
Loader.getResource(Constants.DEFAULT_CONFIGURATION_FILE) != null) {
configurationOptionStr = Constants.DEFAULT_CONFIGURATION_FILE;
}
}
if(debug) {
System.out.println("*** configurationOptionStr=" + configurationOptionStr);
}
//8,初始化配置
IntializationUtil.initialConfiguration(
defaultLoggerRepository, configurationOptionStr, configuratorClassName);
//9,从运行环境获取og4j.repositorySelector配置值
String repositorySelectorStr =
OptionConverter.getSystemProperty("log4j.repositorySelector", null);
//10,如果为空,那么什么都不做,否则判断是否配置值里边是否等于“JNDI”
if (repositorySelectorStr == null) {
// NOTHING TO DO, the default repository has been configured already
} else if (repositorySelectorStr.equalsIgnoreCase("JNDI")) {
if(debug) {
System.out.println("*** Will use ContextJNDISelector **");
}
//11,创建一个ContextJNDISelecto实例和一个Object对象
repositorySelector = new ContextJNDISelector();
guard = new Object();
} else {
//12,实例化repositorySelectorStr
Object r =
OptionConverter.instantiateByClassName(
repositorySelectorStr, RepositorySelector.class, null);
//13,判断是否是RepositorySelector的实例,如果是,那么进行赋值
if (r instanceof RepositorySelector) {
if(debug) {
System.out.println(
"*** Using [" + repositorySelectorStr
+ "] instance as repository selector.");
}
repositorySelector = (RepositorySelector) r;
guard = new Object();
} else {
//
if(debug) {
System.out.println(
"*** Could not insantiate [" + repositorySelectorStr
+ "] as repository selector.");
System.out.println("*** Using default repository selector");
}
// 14 ,穿件一个RepositorySelector接口实现类的实例
repositorySelector = new DefaultRepositorySelector(defaultLoggerRepository);
}
}
if(debug) {
System.out.println("** End of LogManager static initializer");
}
}
通过源码上的14点注释,我们可以知道,目前这段静态代码最主要的逻辑就在initialConfiguration这个方法里边,即注释8处。
首先,我们来看看这个方法的参数: public static void initialConfiguration(LoggerRepository repository, String configuratonResourceStr,String configuratorClassNameStr)
1,参数repository,我们在LogManager传递的这个参数是defaultLoggerRepository指向,即LoggerRepository接口实现类Hierarchy的实例
2,参数configuratonResourceStr,我们从源码上可以知道,这个是log4j.properties 字符串
3,参数 configuratorClassNameStr,是获取系统运行环境log4j.configuratorClass的配置值,这个值我们目前可以认为是空字符串
参数确定之后,我们进入这个方法的内部进行源码的查看:
public static void initialConfiguration(LoggerRepository repository,
String configuratonResourceStr,
String configuratorClassNameStr) {
//1,如果log4j.properties 为空,那么直接返回空
if(configuratonResourceStr == null) {
return;
}
URL url = null;
//2,获取log4j.properties的Url路径对象
try {
url = new URL(configuratonResourceStr);
} catch (MalformedURLException ex) {
// so, resource is not a URL:
// attempt to get the resource from the class loader path
// Please refer to Loader.getResource documentation.
url = Loader.getResource(configuratonResourceStr);
}
// If we have a non-null url, then delegate the rest of the
// configuration to the OptionConverter.selectAndConfigure
// method.
//3,如果url路径对象不为空
if (url != null) {
//4,判断repository是否 LoggerRepositoryEx类的实现类,根据LoggerRepository的继承关系,我们可以知道,我们传入的参数就是它的实例
if (repository instanceof LoggerRepositoryEx) {
LogLog.info(
"Using URL [" + url
+ "] for automatic log4j configuration of repository named ["+
((LoggerRepositoryEx) repository).getName()+"].");
} else {
LogLog.info(
"Using URL [" + url
+ "] for automatic log4j configuration of unnamed repository.");
}
//5,根据参数选择初始化工具
OptionConverter.selectAndConfigure(url, configuratorClassNameStr, repository);
}
}
从源码上可以知道,我们在这里值对参数1,2进行了一些判断和封装(将 log4j.properties封装为Url对象),如何直接将判断过和封装好的参数直接提交给了OptionConverter类
的静态方法selectAndConfigure中,那我们再深入这个方法的源码中:
public static void selectAndConfigure(
URL url, String clazz, LoggerRepository repository) {
Configurator configurator = null;
//1,从url对象中获取文件
String filename = url.getFile();
//2,clazz参数进行非空判断,根据我们上个方法的描述,可以知道这个参数为空,fileName不为空,并且也不是以.xml结尾,那么这段代码我们暂不进入
if ((clazz == null) && (filename != null) && filename.endsWith(".xml")) {
clazz = JoranConfigurator.class.getName();
}
//3,clazz参数进行非空判断,那么这段代码我们也不进入
if (clazz != null) {
Logger logger = repository.getLogger(OptionConverter.class.getName());
logger.info("Preferred configurator class: " + clazz);
configurator =
(Configurator) instantiateByClassName(clazz, Configurator.class, null);
if (configurator == null) {
logger.error("Could not instantiate configurator [" + clazz + "].");
return;
}
} else {
//4,我们直接在这里new了一个PropertyConfigurator类的对象
configurator = new PropertyConfigurator();
}
//5,然后直接调用了PropertyConfigurator对象的doConfigure
configurator.doConfigure(url, repository);
//6,进行判断,我们查看configuratorBase的继承关系
//从继承关系上我们可以知道,这段代码会进入,会调用PropertyConfigurator对象的dumpErrors()
if(configurator instanceof ConfiguratorBase) {
((ConfiguratorBase)configurator).dumpErrors();
}
}
}
从源码的注释上,我们可以了解到,这里的源码只对url和clazz这2个参数进行了一些判断,并根据判断创建了一个configuratorBase类的子类对象,最后调用了这个子类对象
的doConfigure和dumpErrors方法,根据这2个方法的参数,我们可以知道,主要的逻辑必定在doConfigure这个方法中。我们深入到PropertyConfigurator这个类里确定下这个方法的参数: public void doConfigure(java.net.URL configURL, LoggerRepository repository)
1,configURL,我们传递的是由log4j.properties构建的Url对象
2,repository,还是由我们在LogManager上创建的LoggerRepository接口实现类Hierarchy的实例
我们再看下PropertyConfigurator类中的 doConfigure源码,看看这里做了什么:
public void doConfigure(java.net.URL configURL, LoggerRepository repository) {
Properties props = new Properties();
getLogger(repository).debug(
"Reading configuration from URL {}", configURL);
InputStream in = null;
try {
//1,将Url转换为输入流,并将log4j.properties里的配置放入的properties对象中
in = configURL.openStream();
props.load(in);
} catch (Exception e) {
String errMsg =
"Could not read configuration file from URL [" + configURL + "].";
addError(new ErrorItem(errMsg, e));
getLogger(repository).error(errMsg, e);
return;
} finally {
if (in != null) {
try {
in.close();
} catch(IOException ignored) {
}
}
}
//2,将properties对象和repository传入到 doConfigure方法中
doConfigure(props, repository);
}
从上述注释中,我们可以知道,这里只将 log4j.properties配置文件里边的值封装为properties对象,然后将封装好的对象和之前传递的repository参数传递给同名方doConfigure
通过上述源码的阅读,我们发现我们深入代码层级过多,得返回来回顾下,以免弄乱了阅读流程。我们将上述流程转换为时序图如下:
我们总结如下:
1,在调用LogManager中我们将loggerRepository接口实现类Hierarchy对象,配置文件log4j.properties,以及一个configuratorClassNameStr空字符串作为参数传入 initialConfiguration方法中
2,在initialConfiguration方法中,我们将配置文件log4j.properties进行了url对象的封装,同时对LoggerRepository参数进行了一些判断,然后通过 OptionConverter类的
selectAndConfigure
3,在selectAndConfigure方法中,我们队url对象进行了一些判断,同时根据configuratorClassNameStr和url对象的判断,创建了一个ConfiguratorBase子类PropertyConfigurator对象,然后分别调用了这个子类对象的doConfigure和dumpErrors2个方法
4,在doConfigure方法中,我们只是将url对象封装成properties对象,然后就直接调用了同名方法doConfigure
通过总结我们可以知道,代码的主要逻辑已经提交到了doConfigure(Properties properties, LoggerRepository repository) 这个方法中(注意同名方法),这个方法做了那些事?里边有什么逻辑呢?对传入的2个参数进行了做了什么手脚?我们看下回 Log4j源码阅读之四—doConfigure方法