越野车 发表于 2009-2-9 09:48:16

指定类加载器的小技巧

目前java中的类加载是通过委托机制来完成的,也就是说一个类加载器加载一个类的时候会先委托它的父级类装载器去加载,如果他的父亲还有父亲就在委托他父亲的父亲去加载,就这样一直追溯到根类加载器BootStrap,如果BootStrap加载不了目标类,在由BootStrap的下一级去加载,这样一级一级的回退,当回退到最初的类装载器时,如果它自己也不能完成类装载,会抛ClassNotFoundException异常,这样虽然有很多好处,最大的好处就是不容易发生类转换异常,而且类加载器也不会过于混乱,但是有的时候我们希望用指定的类加载器去加载一个类,而不是委托他的父亲。例如如下场景:
src
   com
       syj
          test
             A.java
             B.java
          cp
             MyClassLoader.java
          Main.java
默认情况下上面的程序如果用java Main启动的话。所有类的类加载器都是AppClassLoader由类加载机制可知,如果在Main.java中new A()的话那面A类的加载器也是AppClassLoader了,如果在A中在引入B那B的类加载器也是AppClassLoader,那么如果我想使A类有自己类加载环境的话怎么办呢,我希望指定A类的类加载路径为D盘,B类的加载环境为E盘,这样在A类中依赖的jar文件我可以放在D盘根目录,而B类依赖的jar可以放置在E盘下面。呵呵,这个场景很像tomcat下Webapp中应用隔离的实现,但是tomcat中的实现太复杂了,还没研究过。回归正题,要实现这个需求最初的设想是:只要我们new两个URLClassLoader对象就可以了,然后分别指定两个类加载对象中的类加载路径。在Main.java中分别用两个类加载器加载A类和B类。问题的关键就在这里。A,B和Main类都是在AppClassLoader的类加载路径中,也就是说程序启动靠Main.java类中的main方法,如果程序可以正常启动AppClassLoader就找到了Main.java类,既然找到了Main类那也就可以找到A和B类, URLClassLoader类加载对象是在Main中实例化的,也就是说URLClassLoader类加载器的父类加载器是AppClassLoader由类加载的委托机制可知,如果用URLClassLoader去加载A和B,结果一定还是靠AppClassLoader来加载,而不是我们指定了类加载路径的URLClassLoader类加载器.这样由于A依赖了D盘的jar文件,而AppClassLoader的类路径又没有指定D盘,就会报类找不到的错误.这就是委托机制带来的困扰.
那么如何改实现这样的需求呢,暂时我能想到的就是自己控制类加载的方式,下面是实现方法,顺便把测试代码也写里面了,还有注释:
view plaincopy to clipboardprint?
package com.syj;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import com.syj.test.A;
/**
* <P>
* Title: 自定义的类加载器Demo
* </P>
*
* <P>
* Copyright: Copyright (c) 2007
* </P>
*
* @author 孙钰佳
* @qq 4115291
* @main sunyujia@yahoo.cn
* @date Jun 8, 2008 2:06:15 PM
*/
public class MyClassLoader extends URLClassLoader {
    public MyClassLoader(URL[] urls, ClassLoader parent,
            URLStreamHandlerFactory factory) {
      super(urls, parent, factory);
    }
    public MyClassLoader(URL[] urls, ClassLoader parent) {
      super(urls, parent);
    }
    public MyClassLoader(URL[] urls) {
      super(urls);
    }
    /**
   * 当加载com.syj.test包下的类时先自己找,找不到再使用jvm默认的加载顺序
   */
    protected synchronized Class loadClass(String name, boolean resolve)
            throws ClassNotFoundException {
      if (name.startsWith("com.syj.test"))
            return myLoadClass(name, resolve);
      else
            return super.loadClass(name, resolve);
    }
    /**
   *
   * Description:先判断类是否已经加载,如果没有加载先在自己的URL下找,找不到再使用jvm默认的委托机制查找
   * (注:正常类加载顺序是先找最上层如果找不到依次往下找,这里先找自己如果找不到遵循正常的查找原则,从上向下重找一次)
   *
   * @param name
   * @param resolve
   * @return
   * @throws ClassNotFoundException
   * @author 孙钰佳
   * @since:2008-6-8 下午02:10:07
   */
    protected synchronized Class myLoadClass(String name, boolean resolve)
            throws ClassNotFoundException {
      Class c = findLoadedClass(name);
      if (c == null) {
            try {
                c = findClass(name);
            } catch (ClassNotFoundException e) {
                return super.loadClass(name, resolve);
            }
      }
      return c;
    }
    public static void main(String[] args) throws Exception {
      URL[] url = new URL[] { MyClassLoader.class.getClassLoader()
                .getResource("") };//
      MyClassLoader mc = new MyClassLoader(url);
      // 这地方不能强转换为A,因为不是一个类加载器加载的对象是不能转换的,例如A a=(A),这里的A是AppClassLoader加载的
      System.out.println(A.class.getClassLoader().getClass().getName());// 打印sun.misc.Launcher$AppClassLoader
      Object a = mc.loadClass("com.syj.test.A").newInstance();
      System.out.println(a.getClass().getClassLoader().getClass().getName());// 打印com.syj.MyClassLoader
      // 如果想调用a的方法只能使用反射了
    }
}
页: [1]
查看完整版本: 指定类加载器的小技巧