之前说过使用Spring做web项目时,及时不在web.xml中定义log4j.xml的路径,在实际仍可以正确运行,下面将根据源码来简单分析一下:
1.web.xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <servlet> <servlet-name>maven</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>maven</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
2.将log4j.xml文件放在web-inf/classes下:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Appenders --> <appender name="files" class="org.apache.log4j.DailyRollingFileAppender"> <param name="File" value="${catalina.base}/logs/mytest.log"/> <param name="DatePattern" value="'.'yyyy-MM-dd'.log'"/> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss SSS\} %-5p] [%t] %c{3\} - %m%n"/> </layout> </appender> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%d{yyyy-MM-dd HH:mm:ss SSSS\} %-5p] [%t] %c{3\} - %m%n" /> </layout> </appender> <!-- Application Loggers --> <logger name="org.springframework.samples.mvc"> <level value="info" /> <appender-ref ref="files"/> </logger> <logger name="com.zxhz.maventest"> <level value="info" /> <appender-ref ref="files"/> </logger> <!-- 3rdparty Loggers --> <logger name="org.springframework.core"> <level value="info" /> <appender-ref ref="files"/> </logger> <logger name="org.springframework.beans"> <level value="info" /> <appender-ref ref="files"/> </logger> <logger name="org.springframework.context"> <level value="info" /> <appender-ref ref="files"/> </logger> <logger name="org.springframework.http"> <level value="debug" /> <appender-ref ref="files"/> </logger> <logger name="org.springframework.web"> <level value="debug" /> <appender-ref ref="files"/> </logger> <!-- Root Logger --> <root> <priority value="warn" /> <appender-ref ref="console" /> </root> </log4j:configuration>
3.开始分析过程:
当启动Tomcat的时候,Tomcat会读取web.xml配置文件,根据配置文件加载数据从web.xml文件中我们可以看到Tomcat会先加载
<listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
这个<listener>将会被先运行。再ContextLoaderListener.java这个类里我们可以看到:
package org.springframework.web.context; import javax.servlet.ServletContextEvent; import javax.servlet.ServletContextListener; public class ContextLoaderListener extends ContextLoader implements ServletContextListener { private ContextLoader contextLoader; /** * Initialize the root web application context. */ public void contextInitialized(ServletContextEvent event) { this.contextLoader = createContextLoader(); if (this.contextLoader == null) { this.contextLoader = this; } this.contextLoader.initWebApplicationContext(event.getServletContext()); } //...... }
首先这里会先运行contextInitialized()对其进行初始化操作并且会调用contextLoader的initWebApplicationContext方法并且在initWebApplicationContext方法中会创建一个logger
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { throw new IllegalStateException( "Cannot initialize context because there is already a root application context present - " + "check whether you have multiple ContextLoader* definitions in your web.xml!"); } Log logger = LogFactory.getLog(ContextLoader.class);//初始化log servletContext.log("Initializing Spring root WebApplicationContext"); if (logger.isInfoEnabled()) { logger.info("Root WebApplicationContext: initialization started"); } long startTime = System.currentTimeMillis(); try { //........略1000字。。。。
当执行
Log logger = LogFactory.getLog(ContextLoader.class);
的时候会去调用会去找到LogFactory.getLog方法最终这个方法会通过LogManager类中的静态代码块去调用到Log4j中的Loader.class中的方法完成Log4j的初始化:
static { /*** * .......... */ // if the user has not specified the log4j.configuration // property, we search first for the file "log4j.xml" and then // "log4j.properties" if(configurationOptionStr == null) { url = Loader.getResource(DEFAULT_XML_CONFIGURATION_FILE); if(url == null) { url = Loader.getResource(DEFAULT_CONFIGURATION_FILE); } } else { try { url = new URL(configurationOptionStr); } catch (MalformedURLException ex) { // so, resource is not a URL: // attempt to get the resource from the class path url = Loader.getResource(configurationOptionStr); } } // If we have a non-null url, then delegate the rest of the // configuration to the OptionConverter.selectAndConfigure // method. if(url != null) { LogLog.debug("Using URL ["+url+"] for automatic log4j configuration."); try { OptionConverter.selectAndConfigure(url, configuratorClassName, LogManager.getLoggerRepository()); } catch (NoClassDefFoundError e) { LogLog.warn("Error during default initialization", e); } } else { LogLog.debug("Could not find resource: ["+configurationOptionStr+"]."); } } else { LogLog.debug("Default initialization of overridden by " + DEFAULT_INIT_OVERRIDE_KEY + "property."); } }
Loader.getResource
static public URL getResource(String resource) { ClassLoader classLoader = null; URL url = null; try { if(!java1 && !ignoreTCL) { classLoader = getTCL(); if(classLoader != null) { LogLog.debug("Trying to find ["+resource+"] using context classloader " +classLoader+"."); url = classLoader.getResource(resource); if(url != null) { return url; } } }
这就是为什么我们不在web.xml中定义log4j.xml的配置文件同样也可以正常的使用的原因了