工作中需要解决一些servlet容器类加载器的问题,尤其是Jboss 4.x系列,比方说log4j版本冲突需要靠更改配置项Java2ClassLoadingCompliance,UseJBossWebLoader;ear包部署,出现NoSuchMethodError,NoSuchFieldError,NoClassDefFoundError等(二进制兼容错误)需要进行类隔离(在 JVM加上-XX:+TraceClassLoading -XX:+TraceClassUnloading分析完类的加载卸载verbose)等。除此之外,还有一种ClassLoader问题比较常见。即:如果类库提供了 SPI 接口,并且利用线程上下文类加载器来加载 SPI 实现的 Java 类,有可能会找不到 Java 类。如果出现了 NoClassDefFoundError异常(初始类加载器,定义类加载器问题),则可以先检查当前线程的上下文类加载器是否正确。通过 Thread.currentThread().getContextClassLoader()就可以得到该类加载器。该类加载器应该是该模块对应的类加载器。如果不是的话,可以首先通过 class.getClassLoader()来得到模块对应的类加载器,再通过 Thread.currentThread().setContextClassLoader()来设置当前线程的上下文类加载器。关于上下文加载器,一个显著的使用场景是javax.xml.parsers.DocumentBuilderFactory类中的newInstance方法,跟进去后代码具体如下:
/*
* Try to find provider using Jar Service Provider Mechanism
*
* @return instance of provider class if found or null
*/
private static Object findJarServiceProvider(String factoryId)
throws ConfigurationError
{
String serviceId = "META-INF/services/" + factoryId;
InputStream is = null;
// First try the Context ClassLoader
ClassLoader cl = ss.getContextClassLoader();
boolean useBSClsLoader = false;
if (cl != null) {
is = ss.getResourceAsStream(cl, serviceId);
// If no provider found then try the current ClassLoader
if (is == null) {
cl = FactoryFinder.class.getClassLoader();
is = ss.getResourceAsStream(cl, serviceId);
useBSClsLoader = true;
}
} else {
// No Context ClassLoader, try the current ClassLoader
cl = FactoryFinder.class.getClassLoader();
is = ss.getResourceAsStream(cl, serviceId);
useBSClsLoader = true;
}
if (is == null) {
// No provider found
return null;
}
if (debug) { // Extra check to avoid computing cl strings
dPrint("found jar resource=" + serviceId + " using ClassLoader: " + cl);
}
BufferedReader rd;
try {
rd = new BufferedReader(new InputStreamReader(is, "UTF-8"));
}
catch (java.io.UnsupportedEncodingException e) {
rd = new BufferedReader(new InputStreamReader(is));
}
String factoryClassName = null;
try {
// XXX Does not handle all possible input as specified by the
// Jar Service Provider specification
factoryClassName = rd.readLine();
rd.close();
} catch (IOException x) {
// No provider found
return null;
}
if (factoryClassName != null && !"".equals(factoryClassName)) {
dPrint("found in resource, value=" + factoryClassName);
// Note: here we do not want to fall back to the current
// ClassLoader because we want to avoid the case where the
// resource file was found using one ClassLoader and the
// provider class was instantiated using a different one.
return newInstance(factoryClassName, cl, false, useBSClsLoader);
}
// No provider found
return null;
}
最后,将自己曾经参考过的文献资料分享给大家,希望能对大家有所帮助。
1. https://community.jboss.org/wiki/ClassLoadingConfiguration
3. https://community.jboss.org/wiki/JBossClassLoadingUseCases
4. https://community.jboss.org/wiki/ClassLoadingConfiguration
5. https://community.jboss.org/wiki/EnableClassloaderLogging
7. https://community.jboss.org/wiki/SeparatingApplicationLogs
8. http://javarevisited.blogspot.com/2011/06/noclassdeffounderror-exception-in.html
9. http://www.artima.com/insidejvm/blurb.html
10. http://articles.qos.ch/classloader.html