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 的逻辑是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
public static LogFactory getFactory() throws LogConfigurationException { // Identify the class loader we will be using // 找到应用自身所用的加载器 //在 WAS 5.1 下是 com.ibm.ws.classloader.CompoundClassLoader ClassLoader contextClassLoader = (ClassLoader)AccessController.doPrivileged( new PrivilegedAction() { public Object run() { return getContextClassLoader(); } }); // Return any previously registered factory for this class loader // 看看是否有缓存的与此类加载器关联的 LogFactory 实例,有则返回 LogFactory factory = getCachedFactory(contextClassLoader); if (factory != null) return factory; // Load properties file.. // will be used one way or another in the end. // 加载应用的 Classpath 下的属性文件 commons-logging.properties // FACTORY_PROPERTIES 常量值是 commons-logging.properties // commons-logging 一般在这个文件里指定 LogFactory 的实现类 // 注意,它只是去加载这个属性文件,并不马上用里面配置的 LogFactory 类 Properties props=null; try { InputStream stream = getResourceAsStream(contextClassLoader, FACTORY_PROPERTIES); if (stream != null) { props = new Properties(); props.load(stream); stream.close(); } } catch (IOException e) { } catch (SecurityException e) { } /**** 从下面开始就是 commons-logging 按什么顺找到 LogFactory 实现类 ****/ // First, try the system property // 1. 查找系统属性 FACTORY_PROPERTY(org.apache.commons.logging.LogFactory) // 的值所对应的 LogFactory 实现类 try { String factoryClass = System.getProperty(FACTORY_PROPERTY); if (factoryClass != null) { factory = newFactory(factoryClass, contextClassLoader); } } catch (SecurityException e) { ; // ignore } // Second, try to find a service by using the JDK1.3 jar // discovery mechanism. This will allow users to plug a logger // by just placing it in the lib/ directory of the webapp ( or in // CLASSPATH or equivalent ). This is similar with the second // step, except that it uses the (standard?) jdk1.3 location in the jar. // 2. 使用 JDK1.3 jar 的 Service Provider Interface(SPI) 类发现机制 // 从配置文件 SERVICE_ID(META-INF/services/org.apache.commons.logging.LogFactory) // 的第一行读取 LogFactory 的实现类名 // 这个 META-INF 目录可以是 WebRoot 的 META-INF,也可以是 classpath 下的 META-INF 目录 if (factory == null) { try { InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); if( is != null ) { // This code is needed by EBCDIC and other strange systems. // It's a fix for bugs reported in xerces 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)) { factory= newFactory( factoryClassName, contextClassLoader ); } } } catch( Exception ex ) { ; } } // Third try a properties file. // If the properties file exists, it'll be read and the properties // used. IMHO ( costin ) System property and JDK1.3 jar service // should be enough for detecting the class name. The properties // should be used to set the attributes ( which may be specific to // the webapp, even if a default logger is set at JVM level by a // system property ) // 3. 现在才轮到用前面加载的 commons-logging.properties 文件中的 // FACTORY_PROPERTY(org.apache.commons.logging.LogFactory) 属性指定的 LogFactory 实现类 if (factory == null && props != null) { String factoryClass = props.getProperty(FACTORY_PROPERTY); if (factoryClass != null) { factory = newFactory(factoryClass, contextClassLoader); } } // Fourth, try the fallback implementation class // 4. 前面几步没有找到 LogFactory 的实现类或有异常的话就用默认的实现类 // 即 LogFactory 为我们准备的 FACTORY_DEFAULT(org.apache.commons.logging.impl.LogFactoryImpl) if (factory == null) { factory = newFactory(FACTORY_DEFAULT, LogFactory.class.getClassLoader()); } if (factory != null) { /** * Always cache using context class loader.. * 缓存所用的实现类,以后直接使用缓冲中的 LogFactory 实现类 */ 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; } |
在代码中,我已加上注释,有缓存的 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 通用日志框架时,若出现什么问应具体情况具体分析,相信都不会变离本篇中能解释的情况。
[…] 其中也使用到SPI。从 META-INF/services/org.apache.commons.logging.LogFactory中找要加载的实现类(Apache Commons Logging 是如何决定使用哪个日志实现类的)。 […]
[…] 其中也使用到SPI。从 META-INF/services/org.apache.commons.logging.LogFactory中找要加载的实现类(Apache Commons Logging 是如何决定使用哪个日志实现类的)。 […]
[…] 其中也使用到SPI。從 META-INF/services/org.apache.commons.logging.LogFactory中找要載入的實現類(Apache Commons Logging 是如何決定使用哪個日誌實現類的)。 […]
[…] 其中也使用到SPI。從 META-INF/services/org.apache.commons.logging.LogFactory中找要載入的實現類(Apache Commons Logging 是如何決定使用哪個日誌實現類的)。 […]