前言
最近打算花點時間好好看看spring的源碼,然而現在Spring的源碼經過迭代的版本太多了,比較龐大,看起來比較累,所以准備從最初的版本(interface21)開始入手,僅用於學習,理解其設計思想,后續慢慢研究其每次版本變更的內容。。。
先從interface21的一個典型web工程例子看起,寵物診所 - petclinic,因為該工程基本涵蓋了Spring的APO、IOC、JDBC、Web MVC、事務、國際化、主題切換、參數校驗等主要功能。。。
先從簡單的走起,看下該web工程中, Log4j是如何加載的吧~~~~~~~
對應的web.xml配置
<context-param> <param-name>webAppRootKey</param-name> <param-value>petclinic.root</param-value> </context-param> <context-param> <param-name>log4jConfigLocation</param-name> <param-value>/WEB-INF/classes/log4j.properties</param-value> </context-param> <listener> <listener-class>com.interface21.web.util.Log4jConfigListener</listener-class> </listener>
執行時序圖(看不清的話可以點擊查看原圖)
時序圖中的各個步驟簡要分析
執行的入口在Log4jConfigListener類的contextInitialized方法,由於Log4jConfigListener類實現了ServletContextListener接口,所以在Servlet容器(tomcat)啟動時,會自動調用contextInitialized方法。
步驟描述:
- 進入Log4jConfigListener類的contextInitialized方法,該類只有一句代碼,執行Log4jWebConfigurer.initLogging方法;
public void contextInitialized(ServletContextEvent event) { Log4jWebConfigurer.initLogging(event.getServletContext()); }
- 進入Log4jWebConfigurer類的initLogging方法,首先,調用WebUtils.setWebAppRootSystemProperty方法,內部調用servletContext.getRealPath("/")方法獲取工程實際運行的絕對路徑(如:F:\004_SVN\IBP\springweb\target\spring-web-1.0-SNAPSHOT\),設置到系統變量中(System.setProperty),注意這里的key值是可以配置的,通過webAppRootKey參數配置,如在本例子的web.xml中配成了petclinic.root;
public static void setWebAppRootSystemProperty(ServletContext servletContext) { String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM); String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY); String oldValue = System.getProperty(key); if (oldValue != null) { servletContext.log("WARNING: Web app root system property already set: " + key + " = " + oldValue); servletContext.log("WARNING: Choose unique webAppRootKey values in your web.xml files!"); } else { String root = servletContext.getRealPath("/"); System.setProperty(key, root); servletContext.log("Set web app root system property: " + key + " = " + root); } }
- 獲取日志配置文件路徑、刷新間隔等配置信息,日志配置文件路徑可根據log4jConfigLocation參數配置,這里配置的是相對路徑,通過調用ServletContext.getRealPath()獲得完整路徑,注意getRealPath方法的參數要以“/”開頭;刷新間隔可根據log4jRefreshInterval參數配置,默認為60s;
public static void initLogging(ServletContext servletContext) { // set the web app root system property WebUtils.setWebAppRootSystemProperty(servletContext); // only perform custom Log4J initialization in case of a config file String location = servletContext.getInitParameter(CONFIG_LOCATION_PARAM); if (location != null) { // interpret location as relative to the web application root directory if (location.charAt(0) != '/') { location = "/" + location; } location = servletContext.getRealPath(location); // use default refresh interval if not specified long refreshInterval = Log4jConfigurer.DEFAULT_REFRESH_INTERVAL; String intervalString = servletContext.getInitParameter(REFRESH_INTERVAL_PARAM); if (intervalString != null) { refreshInterval = Long.parseLong(intervalString); } // write log message to server log servletContext.log("Initializing Log4J from " + location); // perform actual Log4J initialization try { Log4jConfigurer.initLogging(location, refreshInterval); } catch (FileNotFoundException ex) { throw new IllegalArgumentException("Invalid log4jConfigLocation parameter: " + ex.getMessage()); } } }
- 進入Log4jConfigurer類的initLogging方法,initLogging比較簡單,根據配置文件后綴名,使用相應的解析器解析配置文件中的元素。
public static void initLogging(String location, long refreshInterval) throws FileNotFoundException { if (!(new File(location)).exists()) { throw new FileNotFoundException("Log4j config file [" + location + "] not found"); } if (location.toLowerCase().endsWith(XML_FILE_EXTENSION)) { DOMConfigurator.configureAndWatch(location, refreshInterval); } else { PropertyConfigurator.configureAndWatch(location, refreshInterval); } }
另外補充下,當Servlet容器銷毀時,會調用Log4jConfigListener的contextDestroyed方法,最終是調用LogManager.shutdown,執行一些資源關閉等操作;