环境
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()
来进行资源的加载。