现在的位置: 首页 > 综合 > 正文

Apache Commons Logging 是如何决定使用哪个日志实现类的

2014年02月25日 ⁄ 综合 ⁄ 共 8715字 ⁄ 字号 评论关闭

Apache Commons Logging 像 SLF4J
一样,是个通用日志框架,广泛应用在各个开源组件中。说其通用,是因为它本身只提供了简单的日志输出的实现
(org.apache.commons.logging.impl.SimpleLog和
org.apache.commons.logging.impl.NoOpLog),主要是为你统一使用其他专业日志实现(Log4j、jdk1.4
Logger、aavalon-Logkit)的方式,让你在程序中看不到具体日志实现的代码,以配置方式解藕。

那么 commons-logging 是怎么决定程序执行时该使用哪个具体的日志实现呢?这里 commons-logging 有两个步骤要做:

1. 定位
org.apache.commons.logging.LogFactory 的实现类(这一步是关键)
2. 定位到的 LogFactory 实现类决定使用哪个 org.apache.commons.logging.Log 实现


那现在我们把注意力主要集中在 commons-logging 如何定位 LogFactory
实现类上来。org.apche.commons.logging.LogFactory 是一个抽象类,所以需要一个 LogFactory 具体类。

通常我们用使用 commons-logging 时是在代码中声明:

Log log =
LogFactory.getLog(UnmiTestLog.class);



getLog() 中是通过 getFactory() 方法获得具体的 LogFactory
实现类,究竟也体现在这个方法中,所以这里非常有必要把这个方法的代码拉出来。下面是 commons-loggin1.0.3 的
LogFactory.getFactory() 代码,在新版代码定位 LogFactory 的逻辑是一样的。



001.
public
static
LogFactory getFactory()
throws
LogConfigurationException {

002.
 
003.
    
// Identify the class loader we will be using

004.
    
// 找到应用自身所用的加载器

005.
    
//在 WAS 5.1 下是
com.ibm.ws.classloader.CompoundClassLoader


006.
    
ClassLoader contextClassLoader =  

007.
        
(ClassLoader)AccessController.doPrivileged(

008.
            
new
PrivilegedAction() {

009.
                
public
Object run() {

010.
                    
return
getContextClassLoader();

011.
                
}

012.
            
});

013.
 
014.
 
015.
    
// Return any previously registered factory for this
class loader


016.
    
// 看看是否有缓存的与此类加载器关联的 LogFactory 实例,有则返回

017.
    
LogFactory factory = getCachedFactory(contextClassLoader);

018.
    
if
(factory !=
null
)

019.
        
return
factory;

020.
 
021.
 
022.
    
// Load properties file..

023.
    
// will be used one way or another in the end.

024.
    
// 加载应用的 Classpath 下的属性文件 commons-logging.properties

025.
    
// FACTORY_PROPERTIES 常量值是 commons-logging.properties

026.
    
// commons-logging 一般在这个文件里指定 LogFactory 的实现类

027.
    
// 注意,它只是去加载这个属性文件,并不马上用里面配置的 LogFactory 类

028.
    
Properties props=
null
;

029.
    
try
{

030.
        
InputStream stream =
getResourceAsStream(contextClassLoader,


031.
                                                 
FACTORY_PROPERTIES);

032.
 
033.
        
if
(stream !=
null
) {

034.
            
props =
new
Properties();

035.
            
props.load(stream);

036.
            
stream.close();

037.
        
}

038.
    
}
catch
(IOException e) {

039.
    
}
catch
(SecurityException e) {

040.
    
}

041.
 
042.
    
/**** 从下面开始就是 commons-logging 按什么顺找到 LogFactory 实现类
****/


043.
 
044.
    
// First, try the system property

045.
    
// 1. 查找系统属性
FACTORY_PROPERTY(org.apache.commons.logging.LogFactory)


046.
    
// 的值所对应的 LogFactory 实现类

047.
    
 
048.
    
try
{

049.
        
String factoryClass =
System.getProperty(FACTORY_PROPERTY);


050.
        
if
(factoryClass !=
null
) {

051.
            
factory = newFactory(factoryClass, contextClassLoader);

052.
        
}

053.
    
}
catch
(SecurityException e) {

054.
        

// ignore

055.
    
}

056.
 
057.
 
058.
    
// Second, try to find a service by using the JDK1.3
jar


059.
    
// discovery mechanism. This will allow users to plug a
logger


060.
    
// by just placing it in the lib/ directory of the
webapp ( or in


061.
    
// CLASSPATH or equivalent ). This is similar with the
second


062.
    
// step, except that it uses the (standard?) jdk1.3
location in the jar.


063.
    
// 2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI)
类发现机制


064.
    
// 从配置文件
SERVICE_ID(META-INF/services/org.apache.commons.logging.LogFactory)


065.
    
// 的第一行读取 LogFactory 的实现类名

066.
    
// 这个 META-INF 目录可以是 WebRoot 的 META-INF,也可以是 classpath
下的 META-INF 目录


067.
 
068.
    
if
(factory ==
null
) {

069.
        
try
{

070.
            
InputStream is = getResourceAsStream(contextClassLoader,

071.
                                                 
SERVICE_ID);

072.
 
073.
            
if
( is !=
null
) {

074.
                
// This code is needed by EBCDIC and other strange
systems.


075.
                
// It's a fix for bugs reported in xerces

076.
                
BufferedReader rd;

077.
                
try
{

078.
                    
rd =
new
BufferedReader(
new
InputStreamReader(is,
"UTF-8"
));

079.
                
}
catch
(java.io.UnsupportedEncodingException e) {

080.
                    
rd =
new
BufferedReader(
new
InputStreamReader(is));

081.
                
}

082.
                
 
083.
                
String factoryClassName = rd.readLine();

084.
                
rd.close();

085.
                
 
086.
                
if
(factoryClassName !=
null
&&

087.
                    
!
""
.equals(factoryClassName)) {

088.
                    
 
089.
                    
factory= newFactory( factoryClassName, contextClassLoader
);


090.
                
}

091.
            
}

092.
        
}
catch
( Exception ex ) {

093.
            
;

094.
        
}

095.
    
}

096.
 
097.
 
098.
    
// Third try a properties file.

099.
    
// If the properties file exists, it'll be read and the
properties


100.
    
// used. IMHO ( costin ) System property and JDK1.3 jar
service


101.
    
// should be enough for detecting the class name. The
properties


102.
    
// should be used to set the attributes ( which may be
specific to


103.
    
// the webapp, even if a default logger is set at JVM
level by a


104.
    
// system property )

105.
    
// 3. 现在才轮到用前面加载的 commons-logging.properties 文件中的

106.
    
//
FACTORY_PROPERTY(org.apache.commons.logging.LogFactory) 属性指定的 LogFactory
实现类


107.
 
108.
    
if
(factory ==
null
  &&  props !=
null
) {

109.
        
String factoryClass = props.getProperty(FACTORY_PROPERTY);

110.
        
if
(factoryClass !=
null
) {

111.
            
factory = newFactory(factoryClass, contextClassLoader);

112.
        
}

113.
    
}

114.
 
115.
 
116.
    
// Fourth, try the fallback implementation class

117.
    
// 4. 前面几步没有找到 LogFactory 的实现类或有异常的话就用默认的实现类

118.
    
// 即 LogFactory 为我们准备的
FACTORY_DEFAULT(org.apache.commons.logging.impl.LogFactoryImpl)


119.
 
120.
    
if
(factory ==
null
) {

121.
        
factory = newFactory(FACTORY_DEFAULT, LogFactory.
class
.getClassLoader());

122.
    
}

123.
    
 
124.
    
if
(factory !=
null
) {

125.
        
/**

126.
         
* Always cache using context class loader..

127.
         
* 缓存所用的实现类,以后直接使用缓冲中的 LogFactory 实现类

128.
         
*/

129.
        
cacheFactory(contextClassLoader, factory);

130.
 
131.
        
if
( props!=
null
) {

132.
            
Enumeration names = props.propertyNames();

133.
            
while
(names.hasMoreElements()) {

134.
                
String name = (String) names.nextElement();

135.
                
String value = props.getProperty(name);

136.
                
factory.setAttribute(name, value);

137.
            
}

138.
        
}

139.
    
}

140.
    
 
141.
    
return
factory;

142.
}

在代码中,我已加上注释,有缓存的 LogFactory 实现类,取缓存中的,注意缓存是与当前应用的类加载器关联的。若缓存中没有的话按
1、2、3、4 的顺序来找,现在就来说说查找 LogFactory 的顺序:

1.
从系统属性中查找键为 org.apache.commons.logging.LogFactory 的值作为 LogFactory
的实现类;却通过 System.getProperty("org.apache.commons.logging.LogFactory") 获得

2. 
使用 JDK1.3 jar 的 Service Provider Interface(SPI) 类发现机制,从配置文件
META-INF/services/org.apache.commons.logging.LogFactory 的的第一行读取
LogFactory 的实现类名。这个
META-INF/services/org.apache.commons.logging.LogFactory 文件可以是某个 Web
应用的根目录中;也可以在 classpath 下,如某个 Jar 包中,WebRoot/WEB-INF/classes 中等。这里需多加留心下
META-INF/services/org.apache.commons.logging.LogFactory 这个目录层次及文件名。

3.  在 Classpath 下的 commons-logging.properties 文件中的,找到
org.apache.commons.logging.LogFactory 属性值作为 LogFactory 实现类

4. 前面三步未找个 LogFactory 的实现类,或有任何异常的情况下,就用默认的实现类,即 LogFactory 为我们准备的
org.apache.commons.logging.impl.LogFactoryImpl

明白了以上的顺序,可以帮助我们理解和解决一些实际的问题,例如,为什么可以不用 commons-logging.properties 也是使用的
log4j 日志实现,部署在 WAS 下的应用 log4j 怎么就不能输出日志了呢?


般,某个具体的 LogFactory 类对应就会使用与其相应的 Logger 实现,如 Log4jFactory.getLog() 得到的是
Log4JLogger 实例,WAS 的 TrLogFactory.getLog() 返回的是 TrLog 实例。

老师们教我们用 commons-logging 时也许会让我们在 classpath 下放一个
commons-logging.properties 文件,并在这个文件中写上一行:

org.apache.commons.logging.LogFactory=org.apache.commons.logging.impl.Log4jFactory

Log4jFactory 已不推荐使用,新的建议的用法是
LogFactory 统一用 LogFactoryImpl,然后在 LogFactoryImpl 中决定声明哪个 Log 实现类

或者是这么两行:

org.apache.cmmons.logging.LogFactory=org.apache.commons.logging.impl.LogFactoryImpl
org.apache.commons.logging.Log=org.apache.commons.logging.impl.Log4JLogger


而我们基本都是用的 Log4j 来输出日志,其实不管 commons-logging.properties
是第一种写法还是第二种写法或许(有时不是) 都是多余的,回望 LogFactory.getFactory() 方法,还要再看看
org.apache.commons.logging.impl.LogFactoryImpl 的 getLogClassName()
方法便可知。

LogFactory.getFactory() 在前面三步找不到 LogFactory 实现类时,就会用默认的
LogFactoryImpl,而默认的 LogFactoryImpl.getLog() 时,又会根据以下四个顺序来决定返回什么 Log
实例(Log 实例对应到实际的日志实现),在 LogFactoryImpl.getLogClassName() 中体现了:

1. commons-logging.properties 中的
org.apache.commons.logging.Log 指定的 Log 实现类
2. Log4j 可用就用 org.apache.commons.logging.impl.Log4JLogger
3. Jdk1.4 Logger 可用就用
org.apcache.commons.logging.impl.Jdk14Logger(JDK1.4 开始自带)
4. SimpleLog 可用就用
org.apache.commons.logging.impl.SimpleLog(commons-logging 自带)



以这就是为什么了,使用了 commons-logging 的框架类,只要扔个 log4j 的 jar,根本不用
commons-logging.properties 文件就会用 log4j 来输出日志,当然 log4j 自己的配置文件 log4j.xml
或 log4j.properties 是需要的。

那为什么在 Tomcat 或别的应用服务器中 log4j 
能正常输出日志,一放到 WAS 5 下却不灵了呢?原因是在 $WAS_HOME/lib/ws-commons-logging.jar
中有个文件 commons-logging.properties,其中有一行
org.apache.commons.logging.LogFactory=com.ibm.ws.commons.logging.TrLogFactory,
虽然你的应用中可能也有一个 commons-logging.properties,可是很不幸,WAS  自己的
commons-logging.properties 优先了,原因是类加载器的委托机制在作用,所以最终 log4j 没派上用场,被
com.ibm.ws.commons.logging.TrLog 取而代之了,解决办法是要抢在它之前,比系统属性中声明 LogFactory
实现类,或是在 META-INF/services/org.apache.commons.logging.LogFactory 中指名
org.apache.commons.logging.impl.Log4jFactory 或
org.apache.commons.logging.impl.LogFactoryImpl 作为实现类名。

以后在使用 commons-logging 通用日志框架时,若出现什么问应具体情况具体分析,相信都不会变离本篇中能解释的情况。

抱歉!评论已关闭.