專案用的是springmvc+spring+mybatis框架,
配置日誌的時候非常簡單,僅僅是把commons-logging、log4j,還有slf4j-log4j三個日誌相關的jar包匯入專案,然後在classpath中加個log4j.properties的配置檔案即可。
但是你有沒有想過Log4j是什麼時候被載入進虛擬機器的?為什麼我們沒有手動載入,spring和mybatis就能自動的用起來log4j,毫不見外?
spring使用的是commons-logging,
mybatis用的是自己寫的,
看一下原始碼,非常簡單,一個個的去試,沒有這個就進入下一個,直到找到了log4j
這兩個工具其實都只是日誌工具的規範,可以理解成介面,而log4j才是一個實實在在的日誌實現。
但是slf4j在什麼地方?——雖然這兩個工具沒用到slf4j,我還是把它放進來,因為我引入的其他工具會用到它。。比如:
下面開始看程式碼程式碼來說明一下log4j是怎麼被載入到虛擬機器的:
首先我們來看spring,我們從web.xml看起,載入spring:
到這裡我們看到了LogFactory,這是commons-logging的一個log工廠,這裡用的是這個虛類的一個靜態方法,我們繼續看這個方法:
再看getFactory(程式碼較長,可以直接跳過先看後邊的結論):
1 public static LogFactory getFactory() throws LogConfigurationException { 2 // Identify the class loader we will be using 3 ClassLoader contextClassLoader = getContextClassLoaderInternal(); 4 5 if (contextClassLoader == null) { 6 // This is an odd enough situation to report about. This 7 // output will be a nuisance on JDK1.1, as the system 8 // classloader is null in that environment. 9 if (isDiagnosticsEnabled()) { 10 logDiagnostic("Context classloader is null."); 11 } 12 } 13 14 // Return any previously registered factory for this class loader 15 LogFactory factory = getCachedFactory(contextClassLoader); 16 if (factory != null) { 17 return factory; 18 } 19 20 if (isDiagnosticsEnabled()) { 21 logDiagnostic( 22 "[LOOKUP] LogFactory implementation requested for the first time for context classloader " + 23 objectId(contextClassLoader)); 24 logHierarchy("[LOOKUP] ", contextClassLoader); 25 } 26 27 // Load properties file. 28 // 29 // If the properties file exists, then its contents are used as 30 // "attributes" on the LogFactory implementation class. One particular 31 // property may also control which LogFactory concrete subclass is 32 // used, but only if other discovery mechanisms fail.. 33 // 34 // As the properties file (if it exists) will be used one way or 35 // another in the end we may as well look for it first. 36 37 Properties props = getConfigurationFile(contextClassLoader, FACTORY_PROPERTIES); 38 39 // Determine whether we will be using the thread context class loader to 40 // load logging classes or not by checking the loaded properties file (if any). 41 ClassLoader baseClassLoader = contextClassLoader; 42 if (props != null) { 43 String useTCCLStr = props.getProperty(TCCL_KEY); 44 if (useTCCLStr != null) { 45 // The Boolean.valueOf(useTCCLStr).booleanValue() formulation 46 // is required for Java 1.2 compatibility. 47 if (Boolean.valueOf(useTCCLStr).booleanValue() == false) { 48 // Don't use current context classloader when locating any 49 // LogFactory or Log classes, just use the class that loaded 50 // this abstract class. When this class is deployed in a shared 51 // classpath of a container, it means webapps cannot deploy their 52 // own logging implementations. It also means that it is up to the 53 // implementation whether to load library-specific config files 54 // from the TCCL or not. 55 baseClassLoader = thisClassLoader; 56 } 57 } 58 } 59 60 // Determine which concrete LogFactory subclass to use. 61 // First, try a global system property 62 if (isDiagnosticsEnabled()) { 63 logDiagnostic("[LOOKUP] Looking for system property [" + FACTORY_PROPERTY + 64 "] to define the LogFactory subclass to use..."); 65 } 66 67 try { 68 String factoryClass = getSystemProperty(FACTORY_PROPERTY, null); 69 if (factoryClass != null) { 70 if (isDiagnosticsEnabled()) { 71 logDiagnostic("[LOOKUP] Creating an instance of LogFactory class '" + factoryClass + 72 "' as specified by system property " + FACTORY_PROPERTY); 73 } 74 factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); 75 } else { 76 if (isDiagnosticsEnabled()) { 77 logDiagnostic("[LOOKUP] No system property [" + FACTORY_PROPERTY + "] defined."); 78 } 79 } 80 } catch (SecurityException e) { 81 if (isDiagnosticsEnabled()) { 82 logDiagnostic("[LOOKUP] A security exception occurred while trying to create an" + 83 " instance of the custom factory class" + ": [" + trim(e.getMessage()) + 84 "]. Trying alternative implementations..."); 85 } 86 // ignore 87 } catch (RuntimeException e) { 88 // This is not consistent with the behaviour when a bad LogFactory class is 89 // specified in a services file. 90 // 91 // One possible exception that can occur here is a ClassCastException when 92 // the specified class wasn't castable to this LogFactory type. 93 if (isDiagnosticsEnabled()) { 94 logDiagnostic("[LOOKUP] An exception occurred while trying to create an" + 95 " instance of the custom factory class" + ": [" + 96 trim(e.getMessage()) + 97 "] as specified by a system property."); 98 } 99 throw e; 100 } 101 102 // Second, try to find a service by using the JDK1.3 class 103 // discovery mechanism, which involves putting a file with the name 104 // of an interface class in the META-INF/services directory, where the 105 // contents of the file is a single line specifying a concrete class 106 // that implements the desired interface. 107 108 if (factory == null) { 109 if (isDiagnosticsEnabled()) { 110 logDiagnostic("[LOOKUP] Looking for a resource file of name [" + SERVICE_ID + 111 "] to define the LogFactory subclass to use..."); 112 } 113 try { 114 final InputStream is = getResourceAsStream(contextClassLoader, SERVICE_ID); 115 116 if( is != null ) { 117 // This code is needed by EBCDIC and other strange systems. 118 // It's a fix for bugs reported in xerces 119 BufferedReader rd; 120 try { 121 rd = new BufferedReader(new InputStreamReader(is, "UTF-8")); 122 } catch (java.io.UnsupportedEncodingException e) { 123 rd = new BufferedReader(new InputStreamReader(is)); 124 } 125 126 String factoryClassName = rd.readLine(); 127 rd.close(); 128 129 if (factoryClassName != null && ! "".equals(factoryClassName)) { 130 if (isDiagnosticsEnabled()) { 131 logDiagnostic("[LOOKUP] Creating an instance of LogFactory class " + 132 factoryClassName + 133 " as specified by file '" + SERVICE_ID + 134 "' which was present in the path of the context classloader."); 135 } 136 factory = newFactory(factoryClassName, baseClassLoader, contextClassLoader ); 137 } 138 } else { 139 // is == null 140 if (isDiagnosticsEnabled()) { 141 logDiagnostic("[LOOKUP] No resource file with name '" + SERVICE_ID + "' found."); 142 } 143 } 144 } catch (Exception ex) { 145 // note: if the specified LogFactory class wasn't compatible with LogFactory 146 // for some reason, a ClassCastException will be caught here, and attempts will 147 // continue to find a compatible class. 148 if (isDiagnosticsEnabled()) { 149 logDiagnostic( 150 "[LOOKUP] A security exception occurred while trying to create an" + 151 " instance of the custom factory class" + 152 ": [" + trim(ex.getMessage()) + 153 "]. Trying alternative implementations..."); 154 } 155 // ignore 156 } 157 } 158 159 // Third try looking into the properties file read earlier (if found) 160 161 if (factory == null) { 162 if (props != null) { 163 if (isDiagnosticsEnabled()) { 164 logDiagnostic( 165 "[LOOKUP] Looking in properties file for entry with key '" + FACTORY_PROPERTY + 166 "' to define the LogFactory subclass to use..."); 167 } 168 String factoryClass = props.getProperty(FACTORY_PROPERTY); 169 if (factoryClass != null) { 170 if (isDiagnosticsEnabled()) { 171 logDiagnostic( 172 "[LOOKUP] Properties file specifies LogFactory subclass '" + factoryClass + "'"); 173 } 174 factory = newFactory(factoryClass, baseClassLoader, contextClassLoader); 175 176 // TODO: think about whether we need to handle exceptions from newFactory 177 } else { 178 if (isDiagnosticsEnabled()) { 179 logDiagnostic("[LOOKUP] Properties file has no entry specifying LogFactory subclass."); 180 } 181 } 182 } else { 183 if (isDiagnosticsEnabled()) { 184 logDiagnostic("[LOOKUP] No properties file available to determine" + " LogFactory subclass from.."); 185 } 186 } 187 } 188 189 // Fourth, try the fallback implementation class 190 191 if (factory == null) { 192 if (isDiagnosticsEnabled()) { 193 logDiagnostic( 194 "[LOOKUP] Loading the default LogFactory implementation '" + FACTORY_DEFAULT + 195 "' via the same classloader that loaded this LogFactory" + 196 " class (ie not looking in the context classloader)."); 197 } 198 199 // Note: unlike the above code which can try to load custom LogFactory 200 // implementations via the TCCL, we don't try to load the default LogFactory 201 // implementation via the context classloader because: 202 // * that can cause problems (see comments in newFactory method) 203 // * no-one should be customising the code of the default class 204 // Yes, we do give up the ability for the child to ship a newer 205 // version of the LogFactoryImpl class and have it used dynamically 206 // by an old LogFactory class in the parent, but that isn't 207 // necessarily a good idea anyway. 208 factory = newFactory(FACTORY_DEFAULT, thisClassLoader, contextClassLoader); 209 } 210 211 if (factory != null) { 212 /** 213 * Always cache using context class loader. 214 */ 215 cacheFactory(contextClassLoader, factory); 216 217 if (props != null) { 218 Enumeration names = props.propertyNames(); 219 while (names.hasMoreElements()) { 220 String name = (String) names.nextElement(); 221 String value = props.getProperty(name); 222 factory.setAttribute(name, value); 223 } 224 } 225 } 226 227 return factory; 228 }
以上程式碼會先試圖從classpath中載入commons-logging.properties等幾個commons-logging才有的東西,直到208行程式碼,
我們看到最後建立了一個LogFactoryImpl的例項,然後返回了。
回頭看看我們上邊的程式碼:
看完了getFactory我們知道最後我們得到的是LogFactoryImpl的例項,
那麼接下來我們該去這個類中看看它的getInstance方法了:
非常簡單,在這裡建立了一個Log的例項,這也是最關鍵的地方,我們去看看這個方法。
經過我個人進一步的程式碼分析logConstructor這個東西是空的,我就不再展開了,這時我們看541行這個方法,這裡才是真正的建立Log例項:
這個方法名我們能看懂,就是去發現系統中的Log實現,也就是一個找的過程,後邊的782行是找找系統變數中有沒有commons-logging自己的指明實現類類名的變數,結果是沒有的,因為我沒有配。
這個方法還沒完,中間是一大段的註釋,我們直接看後邊的重點:
這個時候開始遍歷一個字串陣列,依此去系統中找有沒有這個陣列中的實現類,我們看看這個陣列先。
大家看到了什麼?log4j,現在大家基本已經知道為什麼了,後邊的程式碼如果還有興趣可以自己去看,其實就是一個個的試著去系統中載入這些類,載入不到處理一下ClassNotFoundException然後繼續找下一個。
完畢。
最後如果大家想看看commons-logging自己的日誌,可以寫一個Listener放在web xml第一個位置,把下邊一句程式碼弄上:
你們會從日誌中看到多了很多內容,其中有一句是:
[LogFactoryImpl@1302539661 from org.apache.catalina.loader.WebappClassLoader@1536551745] Class 'org.apache.commons.logging.impl.Log4JLogger' was found at 'jar:file:/E:/eaipweb/wtpwebapps/EAIP/WEB-INF/lib/commons-logging-1.1.1.jar!/org/apache/commons/logging/impl/Log4JLogger.class'
這回真完畢了。