spring日誌載入程式碼解析

劍握在手發表於2017-03-16

專案用的是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'

 

這回真完畢了。

 

相關文章