1 简介
Apache Commons Logging,又名JakartaCommons Logging (JCL),它是Apache提供的一个通用的日志接口,也被称为“门面日志”。“门面日志”利用了设计模式中的门面模式思想,对外提供一套通用的日志记录的 API,而不提供具体的日志输出服务,如果要实现日志输出,需要集成其他的日志框架,比如 Log4j、Logback、Log4j2 等。
这种门面模式的好处在于,记录日志的 API 和日志输出的服务分离开,代码里面只需要关注记录日志的 API,通过 SLF4J 指定的接口记录日志;而日志输出通过引入 JAR 包的方式即可指定其他的日志框架。当我们需要改变系统的日志输出服务时,不用修改代码,只需要改变引入日志输出框架 JAR 包。
JUL最后更新于2014年7月,现以停止更新。
2 包结构
- Log:日志对象接口,封装了操作日志的方法,定义了日志操作的5个级别:trace < debug < info < warn < error
- LogFactory:抽象类,日志工厂,获取日志类;
- LogFactoryImpl:LogFactory的实现类,真正获取日志对象的地方;
- Log4JLogger:对log4j的日志对象封装;
- Jdk14Logger:对JDK1.4的日志对象封装;
- Jdk13LumberjackLogger:对JDK1.3以及以前版本的日志对象封装;
- SimpleLog:commons-logging自带日志对象;
3 日志加载
jcl可以通过在ClassPath下创建commons-logging.properties配置文件指定加载日志实现框架。
#指定日志对象:
org.apache.commons.logging.Log = org.apache.commons.logging.impl.Jdk14Logger
#指定日志工厂:
org.apache.commons.logging.LogFactory = org.apache.commons.logging.impl.LogFactoryImpl
jcl如果没有指定日志实现框架则默认加载时按照顺序log4j>jul> simpleLog依次加载,如果查找到log4j包则使用log4j,如果没有依次类推。
4 JCL使用
引用JCL依赖包
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
测试代码
此时由于项目也没有放入其他日志框架依赖包,会按照顺序log4j>jul> simpleLog加载,因此此时会使用jdk自带jul实现日志打印。
public class commons_loggingDemo {
Log log= LogFactory.getLog(commons_loggingDemo.class);
@Test
public void test() throws IOException {
log.debug("Debug info.");
log.info("Info info");
log.warn("Warn info");
log.error("Error info");
log.fatal("Fatal info");
}
}
引用log4j依赖包
在不修改代码的前提下,引入log4j依赖包。在次调用测试代码,此时会使用log4j实现日志打印。
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
5 JCL原理分析
5.1 LogFactory 类初始化
protected static Hashtable factories = null;
private static PrintStream diagnosticsStream = null;
static {
/** 获取LogFactory类加载器:AppClassLoader设置到thisClassLoader **/
thisClassLoader = getClassLoader(LogFactory.class);
String classLoaderName;
try {
ClassLoader classLoader = thisClassLoader;
if (thisClassLoader == null) {
classLoaderName = "BOOTLOADER";
} else {
classLoaderName = objectId(classLoader);
}
} catch (SecurityException e) {
classLoaderName = "UNKNOWN";
}
diagnosticPrefix = "[LogFactory from " + classLoaderName + "] ";
/** 初始化diagnosticsStream,本质实现为PrintStream负责打印输出**/
diagnosticsStream = initDiagnostics();
/** 获取LogFactory.class的类加载器,并通过diagnosticsStream打印 **/
logClassLoaderEnvironment(LogFactory.class);
/** 实例化存放日志的工厂缓存对象:默认实现为org.apache.commons.logging.impl.WeakHashtable **/
factories = createFactoryStore();
if (isDiagnosticsEnabled()) {
logDiagnostic("BOOTSTRAP COMPLETED");
}
}
实例化存放日志的工厂缓存对象
private static final Hashtable createFactoryStore() {
Hashtable result = null;
String storeImplementationClass;
/** 从系统属性中获取工厂缓存的实现类**/
try {
storeImplementationClass = getSystemProperty(HASHTABLE_IMPLEMENTATION_PROPERTY, null);
} catch (SecurityException ex) {
storeImplementationClass = null;
}
/** 从WEAK_HASHTABLE_CLASSNAME获取工厂缓存的默认实现类**/
if (storeImplementationClass == null) {
storeImplementationClass = WEAK_HASHTABLE_CLASSNAME;
}
/** 实例化工厂缓存实现类对象 **/
try {
Class implementationClass = Class.forName(storeImplementationClass);
result = (Hashtable) implementationClass.newInstance();
} catch (Throwable t) {
handleThrowable(t); // may re-throw t
if (!WEAK_HASHTABLE_CLASSNAME.equals(storeImplementationClass)) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[ERROR] LogFactory: Load of custom hashtable failed");
} else {
System.err.println("[ERROR] LogFactory: Load of custom hashtable failed");
}
}
}
if (result == null) {
result = new Hashtable();
}
/** 返回 结果**/
return result;
}
5.2 从LogFactory获取Log对象
/**
* 获取日志对象
*/
public static Log getLog(Class clazz) throws LogConfigurationException {
/** 得到LogFactoryImpl日志工厂后,实例化具体的日志对象:**/
return getFactory().getInstance(clazz);
}
/**
* 获取日志对象
*/
public static Log getLog(String name) throws LogConfigurationException {
/** 得到LogFactoryImpl日志工厂后,实例化具体的日志对象:**/
return getFactory().getInstance(name);
}
5.3 获取并实例化LogFactory
public static LogFactory getFactory() throws LogConfigurationException {
/** 获取上下文类加载器 **/
ClassLoader contextClassLoader = getContextClassLoaderInternal();
if (contextClassLoader == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("Context classloader is null.");
}
}
/** 从缓存factories中获取LogFactory **/
LogFactory factory = getCachedFactory(contextClassLoader);
if (factory != null) {
return factory;
}
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] LogFactory implementation requested for the first time for context classloader " +
objectId(contextClassLoader));
logHierarchy("[LOOKUP] ", contextClassLoader);
}
/** 读取classpath下的commons-logging.properties文件:**/
Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES);
/**
* 读取commons-logging.properties配置文件中use_tccl属性值
* 如果属性值存在且为flase,设置baseClassLoader指向thisClassLoader,
* 如果不存在use_tccl属性值或属性值为true,baseClassLoader指向上下文类加载器
* **/
ClassLoader baseClassLoader = contextClassLoader;
if (props != null) {
String useTCCLStr = props.getProperty(TCCL_KEY);
if (useTCCLStr != null) {
if (Boolean.valueOf(useTCCLStr).booleanValue() == false) {
baseClassLoader = thisClassLoader;
}
}
}
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY +
"] to define the LogFactory subclass to use...");
}
/**
* 读取commons-logging.properties配置文件中org.apache.commons.logging.LogFactory属性值,作为LogFactory实现类,同时实例化LogFactory对象
**/
try {
String factoryClass = getSystemProperty(FACTORY_PROPERTY, null);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass +
"' as specified by system property " + FACTORY_PROPERTY);
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined.");
}
}
} catch (SecurityException e) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" +
" instance of the custom factory class" + ": [" + trim(e.getMessage()) +
"]. Trying alternative implementations...");
}
} catch (RuntimeException e) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] An exception occurred while trying to create an" +
" instance of the custom factory class" + ": [" +
trim(e.getMessage()) +
"] as specified by a system property.");
}
throw e;
}
/**
* 读取META-INF/services/org.apache.commons.logging.LogFactory配置文件中数据,作为LogFactory实现类,同时实例化LogFactory对象
**/
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID +
"] to define the LogFactory subclass to use...");
}
try {
final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID);
if( is != null ) {
BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
} catch (java.io.UnsupportedEncodingException e) {
rd = new BufferedReader(new InputStreamReader(is));
}
String factoryClassName = rd.readLine();
rd.close();
if (factoryClassName != null && ! "".equals(factoryClassName)) {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " +
factoryClassName +
" as specified by file '" + SERVICE_ID +
"' which was present in the path of the context classloader.");
}
factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader );
}
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found.");
}
}
} catch (Exception ex) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] A security exception occurred while trying to create an" +
" instance of the custom factory class" +
": [" + trim(ex.getMessage()) +
"]. Trying alternative implementations...");
}
}
}
/** 在次读取commons-logging.properties配置文件中org.apache.commons.logging.LogFactory属性值,作为LogFactory实现类,同时实例化LogFactory对象 **/
if (factory == null) {
if (props != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY +
"' to define the LogFactory subclass to use...");
}
String factoryClass = props.getProperty(FACTORY_PROPERTY);
if (factoryClass != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'");
}
factory = newFactory(factoryClass, baseClassLoader, contextClassLoader);
// TODO: think about whether we need to handle exceptions from newFactory
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass.");
}
}
} else {
if (isDiagnosticsEnabled()) {
logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from..");
}
}
}
/** 如果上诉操作没有获取factory实现对象,则使用org.apache.commons.logging.impl.LogFactoryImpl作为默认日志工厂实现类,并实例化工厂对象 **/
if (factory == null) {
if (isDiagnosticsEnabled()) {
logDiagnostic(
"[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT +
"' via the same classloader that loaded this LogFactory" +
" class (ie not looking in the context classloader).");
}
factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader);
}
/** 将LogFactory添加到缓存factories当中: **/
if (factory != null) {
cacheFactory(contextClassLoader, factory);
if (props != null) {
Enumeration names = props.propertyNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
String value = props.getProperty(name);
factory.setAttribute(name, value);
}
}
}
/** 返回 **/
return factory;
}
5.4 从日志工厂获取日志对象:
public Log getInstance(Class clazz) throws LogConfigurationException {
return getInstance(clazz.getName());
}
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;
}
/**
* 构造日志对象构造器函数
*/
protected Constructor logConstructor = null;
/**
* The signature of the Constructor to be used.
*/
protected Class logConstructorSignature[] = { String.class };
/**
* 实例化日志对象反相依赖日志工厂的方法<code>setLogFactory</code>
*/
protected Method logMethod = null;
protected Log newInstance(String name) throws LogConfigurationException {
Log instance;
try {
/** 如果构造器不存在,调用discoverLogImplementation尝试通过类名称实例化日志对象 **/
if (logConstructor == null) {
instance = discoverLogImplementation(name);
}
/** 如果构造器存在,使用构造器实例化日志对象 **/
else {
Object params[] = { name };
instance = (Log) logConstructor.newInstance(params);
}
/** 如果实例化日志对象,存在setLogFactory方法,则调用和构日志工厂方法反相关联 **/
if (logMethod != null) {
Object params[] = { this };
logMethod.invoke(instance, params);
}
return instance;
} catch (LogConfigurationException lce) {
throw lce;
} catch (InvocationTargetException e) {
Throwable c = e.getTargetException();
throw new LogConfigurationException(c == null ? e : c);
} catch (Throwable t) {
handleThrowable(t);
throw new LogConfigurationException(t);
}
}
private Log discoverLogImplementation(String logCategory)
throws LogConfigurationException {
if (isDiagnosticsEnabled()) {
logDiagnostic("Discovering a Log implementation...");
}
initConfiguration();
Log result = null;
/** 从classpath中的commons-logging.properties文件中获取“org.apache.commons.logging.Log”的value:**/
String specifiedLogClassName = findUserSpecifiedLogClassName();
/** 如果配置文件中存在日志实现类,那么就进行日志对象实例化: **/
if (specifiedLogClassName != null) {
if (isDiagnosticsEnabled()) {
logDiagnostic("Attempting to load user-specified log class '" +
specifiedLogClassName + "'...");
}
result = createLogFromClass(specifiedLogClassName,
logCategory,
true);
if (result == null) {
StringBuffer messageBuffer = new StringBuffer("User-specified log class '");
messageBuffer.append(specifiedLogClassName);
messageBuffer.append("' cannot be found or is not useable.");
// Mistyping or misspelling names is a common fault.
// Construct a good error message, if we can
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LOG4J_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_JDK14_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_LUMBERJACK_LOGGER);
informUponSimilarName(messageBuffer, specifiedLogClassName, LOGGING_IMPL_SIMPLE_LOGGER);
throw new LogConfigurationException(messageBuffer.toString());
}
return result;
}
if (isDiagnosticsEnabled()) {
logDiagnostic(
"No user-specified Log implementation; performing discovery" +
" using the standard supported logging implementations...");
}
/**
*如果配置文件中不存在“org.apache.commons.logging.Log”的value:
*那么还有遍历classesToDiscover字符串数组,进行实例化:
*此数组中包括:log4j、Jdk14Logger、Jdk13LumberjackLogger、SimpleLog
**/
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
/** 如果此时日志对象还未null,为报错; **/
if (result == null) {
throw new LogConfigurationException
("No suitable Log implementation");
}
return result;
}
commons-logging获取日志对象的全过程
获取当前线程的classLoader,根据classLoader从缓存中获取LogFactroy,使用的缓存是WeakHashTable对象;如果缓存中存在,则返回,没有则进入下面流程;
读取classpath下的commons-logging.properties文件,判断其中是否设置了use_tccl属性,如果不为空则判断,该属性的值是否为false,若为false,则将baseClassLoader替换为当前类的classLoader;
-
接着,继续获取LogFactory对象,此步骤分为4中方式:
- (1)在系统属性中查找“org.apache.commons.logging.LogFactory”属性的值,根据值生成LogFactory对象;
- (2)通过资源“META-INF/services/org.apache.commons.logging.LogFactory”文件,获取的值生成LogFactory对象;
- (3)通过配置文件commons-logging.properties,获取以“org.apache.commons.logging.LogFactory”为key的值,根据值生成logFactory;
- (4)如果以上均不成功,则创建系统默认的日志工厂:org.apache.commons.logging.impl.LogFactoryImpl
成功获取日志工厂后,根据类名获取日志对象;
-
主要逻辑在discoverLogImplementation方法中:
- (1)检查commons-logging.properties文件中是否存在“org.apache.commons.logging.Log”属性,若存在则创建具体的日志对象;若不存在,进行下面逻辑;
- (2)遍历classesToDiscover数组,该数组存有日志具体实现类的全限定类名:org.apache.commons.logging.impl.Log4JLogger、org.apache.commons.logging.impl.Jdk14Logger、org.apache.commons.logging.impl.Jdk13LumberjackLogger、org.apache.commons.logging.impl.SimpleLog;
- (3)根据数组中存着的全限定类名,按照顺序依次加载Class文件,进行实例化操作,最后返回Log实例,默认为Jdk14Logger;
6 缺陷
JCL饱受诟病最多的是获取日志工厂的过程中使用了classLoader来寻找日志工厂实现,进而导致了其他组件,如若使用自己的classload,则不能获取具体的日志工厂对象,则导致启动失败,这样就是我们常说的--动态查找机制。