源起:开发了一个Package,这个Package用到了一个框架,该框架封装了log4J的日志功能,log4J初始化需要加载classpath下的log4j.properties,将这个properties文件到Package的code/classes下,或者config下均无法找到。
限制:不能跨越框架的封装直接调用log4J的初始化类来初始化log4J。
版本:webMethods IS 6.1, log4J 1.2.8
log4J在第一次调用LogManager的时候最始化,代码:
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);
}
}
这里的Loader是helpers下面的Loader类,getResource方法的代码如下:
static public URL getResource(String resource) {
ClassLoader classLoader = null;
URL url = null;
try {
if(!java1) {
classLoader = getTCL();
if(classLoader != null) {
LogLog.debug("Trying to find ["+resource+"] using context classloader "
+classLoader+".");
url = classLoader.getResource(resource);
if(url != null) {
return url;
}
}
}
// We could not find resource. Ler us now try with the
// classloader that loaded this class.
classLoader = Loader.class.getClassLoader();
if(classLoader != null) {
LogLog.debug("Trying to find ["+resource+"] using "+classLoader
+" class loader.");
url = classLoader.getResource(resource);
if(url != null) {
return url;
}
}
} catch(Throwable t) {
LogLog.warn(TSTR, t);
}
// Last ditch attempt: get the resource from the class path. It
// may be the case that clazz was loaded by the Extentsion class
// loader which the parent of the system class loader. Hence the
// code below.
LogLog.debug("Trying to find ["+resource+
"] using ClassLoader.getSystemResource().");
return ClassLoader.getSystemResource(resource);
}
大家可以看到,classLoader = Loader.class.getClassLoader(); 实际上log4J是调用当前类的ClassLoader来装载这个properties文件的。特别注意的是它使用的是getResource()方法,该方法返回一个URL类型的对象。
接下来看webMethods的ClassLoarder,webMethods IS有一个Server的ClassLoader,每一个Package有一个自己的ClassLoader,值得注意的是Package的ClassLoader与整个Server的ClassLoader没有继承关系,它们都是com.wm.app.b2b.server.ServerClassLoader的实例,这个类直接继承自JDK的ClassLoader,但是覆盖了父类的getResourceAsStream方法,代码如下:
public InputStream getResourceAsStream(String name)
{
for(int i = 0; i < resdirs.size(); i++)
{
File rfile = new File((File)resdirs.elementAt(i), name);
if(rfile.exists())
try
{
return new FileInputStream(rfile);
}
catch(IOException _ex)
{
JournalLogger.log(27, 28, name);
}
}
for(int i = 0; i < jars.size(); i++)
try
{
ZipFile zfile = new ZipFile((File)jars.elementAt(i));
ZipEntry ze = zfile.getEntry(name);
if(ze != null)
{
InputStream is = null;
is = zfile.getInputStream(ze);
return is;
}
}
catch(Exception _ex)
{
JournalLogger.log(26, 28, name);
}
for(int i = 0; i < cldirs.size(); i++)
{
File cfile = new File((File)cldirs.elementAt(i), name);
if(cfile.exists())
try
{
return new FileInputStream(cfile);
}
catch(IOException _ex)
{
JournalLogger.log(27, 28, name);
}
}
return null;
}
其中变量“resdirs”,“jars”,“cldirs”都是Vertor,通过下面的方法最始初化:
public static void addPackage(String pkgName)
throws IOException, ZipException
{
Resources res = Server.getResources();
ServerClassLoader scl = new ServerClassLoader(pkgName);
scl.addJar(new File(res.getPackageCodeDir(pkgName), "classes.zip"));
scl.addJarDir(res.getPackageJarsDir(pkgName));
scl.addClassDir(res.getPackageClassDir(pkgName));
scl.addResourceDir(res.getPackageResourceDir(pkgName));
scl.addResourceDir(res.getDeprecatedPackageResourceDir(pkgName));
pkgs.put(pkgName, scl);
}
private void addResourceDir(File dir)
{
if(dir != null || dir.isDirectory())
resdirs.addElement(dir);
}
查看Resource类的代码发现放在Package的code/classes下,code/jars下,config下和lib下的文件都可以通getResourceAsStream这个方法取到。不知道什么原因,webMethods实现的ServerClassLoader并没有覆盖getResource方法,调用这个方法实际调用的是其父类即JDK的ClassLoader的方法。
问题就出在这里,当我在Package里用log4J的时候,log4J的实现是用getResource方法的,因此放在Package的资源文件下的properties文件无法被找到,只能将这个文件放到整个Server级别的classpath下。
影响:整个webMethods IS如果有多个包用自己的log4J,但却只能有一个log4J的配置。即使设置log4j.configuration这个属性,也只能有一个。