ClassLoader解决jar包冲突问题

咱们知道,Java利用ClassLoader将类载入内存,而且在同一应用中,能够有不少个ClassLoader,经过委派机制,把装载的任务传递给上级的装载器的,依次类推,直到启动类装载器(没有上级类装载器)。若是启动类装载器可以装载这个类,那么它会首先装载。若是不能,则往下传递。当父类为null时,JVM内置的类(称为:bootstrap class loader)就会充当父类。想一想眼下的愈来愈多用XML文件作配置文件或者是描述符、部署符。其实这些经过XML文档描述的配置信息最终都要变成Java类,基实都是经过ClassLoader来完成的。URLClassLoader是ClassLoader的子类,它用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。也就是说,经过URLClassLoader就能够加载指定jar中的class到内存中。

下面来看一个例子,在该例子中,咱们要完成的工做是利用URLClassLoader加载jar并运行其中的类的某个方法。java

首先咱们定义一个接口,使全部继承它的类都必须实现action方法,以下:web

public interface ActionInterface {
     public String action();
}
完成后将其打包为testInterface.jar文件。bootstrap

接下来新建一工程,为了编译经过,引入以前打好的testInterface.jar包。并建立TestAction类,使它实现ActionInterface接口。以下:api


public class  TestAction implements ActionInterface {
    public  String action() {
         return " com.mxjava.TestAction.action " ;
    }
}
 
完成后将其打包为test.jar,放在c盘根目录下。下面要作的就是利用URLClassLoader加载并运行TestAction的action方法,并将返回的值打印在控制台上。服务器

新建一工程,引入testInterface.jar包。并建立一可执行类(main方法),在其中加入以下代码:app

URL url = new URL(“file:C: / test.jar”);
URLClassLoader myClassLoader  =   new  URLClassLoader( new  URL[]   { url } );
Class myClass  =  myClassLoader.loadClass(“com.mxjava.TestAction”);
ActionInterface action  =  (ActionInterface)myClass.newInstance();
System.out.println(action.action());
  在上面的例子中,首先利用URLClassLoader加载了C:/test.jar包,将其中的com.mxjava.TestAction类载入内存,将其强制转型为testInterface包中的ActionInterface类型,最后调用其action方法,并打印到控制台中。url

  执行程序后,在控制台上如期打印出咱们想要的内容。可是,事情并无那么简单,当咱们将该代码移动web应用中时,就会抛出异常。原来,Java为咱们提供了三种可选择的ClassLoader:
1. 系统类加载器或叫做应用类加载器 (system classloader or application classloader)
2. 当前类加载器
3. 当前线程类加载器spa

  在上例中咱们使用javac命令来运行该程序,这时候使用的是系统类加载器 (system classloader)。这个类加载器处理 -classpath下的类加载工做,能够经过ClassLoader.getSystemClassLoader()方法调用。 ClassLoader 下全部的 getSystemXXX()的静态方法都是经过这个方法定义的。在代码中,应该尽可能少地调用这个方法,以其它的类加载器做为代理。不然代码将只能工做在简单的命令行应用中。当在web应用中时,服务器也是利用ClassLoader来加载class的,因为ClassLoader的不一样,因此在强制转型时JVM认定不是同一类型。(在JAVA中,一个类用其彻底匹配类名(fully qualified class name)做为标识,这里指的彻底匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例做为惟一标识。所以,若是一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不一样的,被它们所加载的类也所以彻底不一样,互不兼容的。)为了可以使程序正确运行,咱们首要解决的问题就是,如何将URLClassLoader加载的类,同当前ClassLoader保持在同一类加载器中。解决方法很简单,利用java提供的第三种ClassLoader—当前线程类加载器便可。jdk api文档就会发现,URLClassLoader提供了三种构造方式:命令行

 // 使用默认的委托父 ClassLoader 为指定的 URL 构造一个新 URLClassLoader。 
 URLClassLoader(URL[] urls)
 // 为给定的 URL 构造新 URLClassLoader。 
 URLClassLoader(URL[] urls, ClassLoader parent)
 // 为指定的 URL、父类加载器和 URLStreamHandlerFactory 建立新 URLClassLoader。
 URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) 
接下来要作的就是,在构造URLClassLoader时,将当前线程类加载器置入便可。以下:线程

  URLClassLoader myClassLoader  =   new  URLClassLoader( new  URL[]   { url } , Thread.currentThread().getContextClassLoader()); 总结:  Java是利用ClassLoader来加载类到内存的,ClassLoader自己是用java语言写的,因此咱们能够扩展本身的ClassLoader。利用URLClassLoader能够加载指定jar包中的类到内存。在命行上利用URLClassLoader加载jar时,是使用系统类加载器来加载class的,因此在web环境下,就会出错。这是由于JVM中一个类用其全名和一个加载类ClassLoader的实例做为惟一标识的。咱们只要利用URLClassLoader的第二种构造方法并传入当前线程类加载器便可解决。