问题描述
最近在storm上面做开发,碰到一个问题:bolt需要动态加载jar,然后调用新加载jar包的方法。结果发现一个问题,在这个jar中会调用mybatis的函数,结果问题就出现了: mybatis的class无法加载,抛出ClassNotFoundException,
因为storm本身的存在classpath的问题,采取了网友给出的方法,使用assembly的方法打包,但是问题还是无法解决。刚开始怀疑是storm的问题(无知的一种表现),后来仔细走读代码,发现在classloader的时候:
public static TestIfc load (String jarPath, String mainCls, String args)
{
URLClassLoader myClassLoader1 = null;
try{
URL url1 = new URL("file:" + jarPath);
myClassLoader1 = new URLClassLoader(new URL[] { url1 }, Thread.currentThread()
.getContextClassLoader());
Class<?> myClass1 = myClassLoader1.loadClass(mainCls);
TestIfc action1 = (TestIfc) myClass1.newInstance();
return action1;
}
catch(Exception e)
{
e.printStackTrace();
}finally{
if(myClassLoader1 != null){
try {
myClassLoader1.close(); //这里不知道为啥加了一个close函数
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
return null;
}
试着将finaly的代码屏蔽掉,问题解决。
分析
在《深入理解Java虚拟机 JVM高级特性与最佳实践》中提到“遇到new、getstatic、putstatic或invokestatic这4条字节码指令时,如果类没有进行初始化,则需要先触发初始化”,初始化的时候,就会调用loadClass代码。 而我们的代码抛出的异常也是loadClass抛出。
查看loadClass的源码,网上查找了一下关于这部代码的注 (http://www.cnblogs.com/ericchen/archive/2011/01/15/1936130.html)
protected synchronized Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// 首先检查,jvm中是否已经加载了对应名称的类,findLoadedClass(String )方法实际上是findLoadedClass0方法的wrapped方法,做了检查类名的工
//作,而findLoadedClass0则是一个native方法,通过底层来查看jvm中的对象。
Class c = findLoadedClass(name);
if (c == null) {//类还未加载
try {
if (parent != null) {
//在类还未加载的情况下,我们首先应该将加载工作交由父classloader来处理。
c = parent.loadClass(name, false);
} else {
//返回一个由bootstrap class loader加载的类,如果不存在就返回null
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
c = findClass(name);//这里是我们的入手点,也就是指定我们自己的类加载实现,也就是调用URLClassLoader的findClass()
}
}
if (resolve) {
resolveClass(c);//用来做类链接操作
}
return c;
}
再次查看URLClassLoader的findClass函数
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
try {
return AccessController.doPrivileged(
new PrivilegedExceptionAction<Class>() {
public Class run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
//从ucp中查找到path资源
Resource res = ucp.getResource(path, false);
....
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
}
而ucp的值是在URLClassLoader初始化的时候创建的:
public URLClassLoader(URL[] urls, ClassLoader parent) {
super(parent);
... ....
ucp = new URLClassPath(urls);
this.acc = AccessController.getContext();
}
而在我们的代码中调用close()函数,将将ucp close掉
public void close() throws IOException {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(new RuntimePermission("closeClassLoader"));
}
List<IOException> errors = ucp.closeLoaders();
... ...
}
正是因为ucp的值被close掉,因此导致URLClassLoader在find的时候,无法查找到相应的class,因此抛出异常。
总结
URLClassLoader一般属于用户的cloassloader,而在这个loader中,JAVA为将jar包的信息都存放在ucp的这个URLClassPath中。 而在加载的时候,首先通过双亲委派的模式加载,当父加载器无法加载的时候,会调用用户加载器。 而这个用户加载器,就通过URLClassPath进行查找,如果找到,则加载,无法无法找到则抛出异常