NoClassDefFoundError: org/apache/log4j/spi/LoggerFactory ESAPI 问题排查笔记

一、背景介绍
     
在原有项目中使用的是log4j,为了保证与其他项目日式输出方式保持一致,准备将log4j升级为logback,增加日志滚动存储方式。

二、问题说明
     
在测试人员进行功能验证时,发现进行商品搜索时出现如下异常:

Caused by: java.lang.NoClassDefFoundError: org/apache/log4j/spi/LoggerFactory
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:259)
at org.owasp.esapi.util.ObjFactory.make(ObjFactory.java:74)
at org.owasp.esapi.ESAPI.logFactory(ESAPI.java:137)
at org.owasp.esapi.ESAPI.getLogger(ESAPI.java:154)
at org.owasp.esapi.reference.DefaultEncoder.<init>(DefaultEncoder.java:75)
at org.owasp.esapi.reference.DefaultEncoder.getInstance(DefaultEncoder.java:59)
... 66 common frames omitted
Caused by: java.lang.ClassNotFoundException: org.apache.log4j.spi.LoggerFactory
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1333)
at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1167)
... 73 common frames omitted

由于项目中使用了esapi工具jar对输入内容进行了过滤。
从错误代码初步定位为由于esapi需要使用log4j,由于在项目进行log4j升级为logback时,将项目中的所有log4j的jar包全部排除导致问题的产生。
疑问:难道esapi工具强依赖log4j,没有log4j无法独立使用?

三、问题解决
     将logback回滚为log4j这个方案实在不可行,只有研究esapi源码来解决问题

1、在esapi中获取logger方式:

protected final Logger logger = ESAPI.getLogger("xxxxxx");

2、代码逐步跟进到org.owasp.esapi.ESAPI类:

/**
 * @param moduleName The module to associate the logger with.
 * @return The current Logger associated with the specified module.
 */
public static Logger getLogger(String moduleName) {
	return logFactory().getLogger(moduleName);
}

/**
 * Get the current LogFactory being used by ESAPI. If there isn't one yet, it will create one, and then 
 * return this same LogFactory from then on.
 * @return The current LogFactory being used by ESAPI.
 */
private static LogFactory logFactory() {
	return ObjFactory.make( securityConfiguration().getLogImplementation(), "LogFactory" );
}
/**
 * @return the current ESAPI SecurityConfiguration being used to manage the security configuration for 
 * ESAPI for this application. 
 */
public static SecurityConfiguration securityConfiguration() {
	// copy the volatile into a non-volatile to prevent TOCTTOU race condition
	SecurityConfiguration override = overrideConfig;
	if ( override != null ) {
		return override;
	}

	return ObjFactory.make( securityConfigurationImplName, "SecurityConfiguration" );
}

private static String securityConfigurationImplName = System.getProperty("org.owasp.esapi.SecurityConfiguration", "org.owasp.esapi.reference.DefaultSecurityConfiguration");

注:ObjFactory.make()的这个方法底层就是通过反射方式创建对象实例,供系统使用,这里不做过多解释
从以上代码可以得知logger来源于logFactory来源于org.owasp.esapi.reference.DefaultSecurityConfiguration配置

3、代码继续向DefaultSecurityConfiguration进行追踪:

/**
 * Instantiates a new configuration.
 */
public DefaultSecurityConfiguration() {
	// load security configuration
	try {
		loadConfiguration();
		this.setCipherXProperties();
	} catch( IOException e ) {
		logSpecial("Failed to load security configuration", e );
		throw new ConfigurationException("Failed to load security configuration", e);
	}
}
/**
 * Load configuration. Never prints properties.
 * 
 * @throws java.io.IOException
 *             if the file is inaccessible
 */
protected void loadConfiguration() throws IOException {
	
	......
	//first attempt file IO loading of properties
	logSpecial("Attempting to load " + RESOURCE_FILE + " via file I/O.");
	properties = loadPropertiesFromStream(getResourceStream(RESOURCE_FILE), RESOURCE_FILE);
	......
}

/** The name of the ESAPI property file */
public static final String RESOURCE_FILE = "ESAPI.properties";

从以上代码可知道在DefaultSecurityConfiguration进行初始化的时候会默认读取项目中配置的ESAPI.properties配置文件,并将配置文件内容存储到properties属性中,供系统使用

4、接着第1步继续追踪securityConfiguration().getLogImplementation()方法,代码如下所示:

public static final String LOG_IMPLEMENTATION = "ESAPI.Logger";
public static final String DEFAULT_LOG_IMPLEMENTATION = "org.owasp.esapi.reference.JavaLogFactory";
/**
 * {@inheritDoc}
 */
public String getLogImplementation() {
	return getESAPIProperty(LOG_IMPLEMENTATION, DEFAULT_LOG_IMPLEMENTATION);
}
protected String getESAPIProperty( String key, String def ) {
	String value = properties.getProperty(key);
	if ( value == null ) {
		logSpecial( "SecurityConfiguration for " + key + " not found in ESAPI.properties. Using default: " + def, null );
		return def;
	}
	return value;
}

从以上代码可知,esapi会在properties属性中查找是否有配置ESAPI.Logger这个属性,如果未配置默认使用org.owasp.esapi.reference.JavaLogFactory做为LogFactory使用。
到这一步问题已经非常明了,经排查发现项目中确实配置了ESAPI.properties配置文件,并且将ESAPI.Logger=org.owasp.esapi.reference.Log4JLogFactory

5、继续对org.owasp.esapi.reference.Log4JLogFactory 进行代码追踪:

import org.apache.log4j.spi.LoggerFactory;
//The Log4j logger factory to use
LoggerFactory factory = new Log4JLoggerFactory();
/**
* {@inheritDoc}
*/
public org.owasp.esapi.Logger getLogger(String moduleName) {
	return (org.owasp.esapi.Logger)LogManager.getLogger(moduleName, factory);
}

由于项目中目前已无log4j工具jar的依赖,所以导致在进行org.owasp.esapi.reference.Log4JLogFactory初始化时报出Caused by: java.lang.NoClassDefFoundError: org/apache/log4j/spi/LoggerFactory异常

四、问题结论

      esapi jar包非强依赖log4j,由于ESAPI.properties>ESAPI.Logger属性配置不当导致异常产生。esapi中LoggerFactory默认实现方式是org.owasp.esapi.reference.JavaLogFactory,在使用logback日志输出方式时,请着重关注ESAPI.Logger配置。防止掉坑。

猜你喜欢

转载自blog.csdn.net/TimerBin/article/details/86499716
今日推荐