如何让父加载器调用子加载器

我们的影子在花园石径上徘徊,比那些活着的人更具生气。
- 双亲委派很好的解决了各个类加载器的基础类的统一问题(越基础的类由越上层的加载器进行加载)。但是如果基础类需要回调用户代码,该怎么办?比如有两个类。《深入理解JVM》一书中给出如下说明。

为了解决这个问题,JAVA设计团队只好引入了一个不太优雅的设计:线程上下文类加载器(Thread Context ClassLoader)。这个类加载器可以通过java.lang.Thread类的setContextClassLoader()方法进行设置。有了线程上下文类加载器,就可以做一些舞弊的事情了,JNDI服务使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器去完成类加载的动作。
Thread.currentThread().getContextClassLoader()源码注释为:此方法返回Thread的上下文ClassLoader。它由线程的创建者提供,一遍在加载类和资源时在此线程中运行的代码使用。它默认为父线程的Context ClassLoader. 原始线程的Context ClassLoader通常设置为用于加载应用程序的类加载器。如果存在安全管理器,并且调用者的类加载器不为空,并且与上下文类加载器的祖先不一样,则该方法使用RuntimePermission(“getClassLoader”)权限调用安全管理器的checkPermission方法来验证 允许上下文类加载器的检索。

  • 一个例子

    public class Animal {
        static{ System.out.println("Animal is loaded");}
        static int b =  Cat.a;
    }
    
    public class Cat extends Animal{
        public static int a = 1;
        static{System.out.println("Cat is loaded");}
    }
    
    AnimalLoad.java 
    
    private String filePath = "../Animalpath/";
    
    protected Class findClass(String name) throws ClassNotFoundException {
        System.out.println("AnimalLoad load " + name);
    
        Class result = null;
        if(name.contains("Animal")){
            String fp = filePath + name + ".class";
            try {
                byte[] bytes = getClassBytes(getClassFile(fp));
                result = this.defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        if(result == null){
            result = Thread.currentThread().getContextClassLoader().loadClass(name);
        }
        return result;
    }
    
    CatLoad.java
    private String filePath = "../CatPath/";
    
    protected Class findClass(String name) throws ClassNotFoundException {
        System.out.println("CatLoad load " + name);
    
        Class result = null;
        if (name.contains("Cat")) {
            String fp = filePath + name + ".class";
            try {
                byte[] bytes = getClassBytes(getClassFile(fp));
                result = this.defineClass(name, bytes, 0, bytes.length);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return result;
    }
    
    protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            Class c = findLoadedClass(name);
            if (c == null) {
                //先让子加载器加载所需要的类,否则会形成死循环
                c = findClass(name);
                if (c == null && getParent() != null) {c = getParent().loadClass(name);}
            }
            if (resolve) {resolveClass(c);}
            return c;
        }
    }
    
    Test:
    
    AnimalLoad animalLoad = new AnimalLoad();
    CatLoad catLoad = new CatLoad(animalLoad);
    Thread.currentThread().setContextClassLoader(catLoad);
    catLoad.loadClass("Cat").newInstance();
    
    输出:
    
    CatLoad load Cat
    CatLoad load Animal
    AnimalLoad load Animal
    Animal is loaded
    AnimalLoad load Cat
    CatLoad load java.lang.System
    CatLoad load java.io.PrintStream
    Cat is loaded
    
  • 为什么不让AnimalLoad直接new一个CatLoad呢,因为这是两个不同的加载器,加载出的来的类不是同一个类。

    CatLoad catLoad01 = new CatLoad(); CatLoad catLoad02 = new CatLoad();
    System.out.println(catLoad01.loadClass(“Cat”).isAssignableFrom(catLoad02.loadClass(“Cat”)));
    输出:false

  • 如果有类A,类B。A使用了B,B使用了A。A需要放在本地,通过-cp命令指定路径,B需要通过网络动态加载,则如何做呢?

    ClassLoader.getSystemClassLoader()是一个AppClassLoader。这一点可以通过System.out.println(ClassLoader.getSystemClassLoader())验证。AppClassLoader是URLClassLoader的子类。Bootstrapper通过反射调用URLClassLoader的addURL方法,把B的Class路径添加进去,则系统类加载器可以直接加载A&B。

    public class Bootstrapper {
        public static void main(String[] args) throws Exception {
    
            if(args.length < 3){
                throw new Exception("参数个数异常 :args.length is " + args.length);
            }
    
            URL[] urls=buildURLs(args[0],args[1]);
    
            Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
            method.setAccessible(true);
            for(URL url : urls){
                method.invoke(ClassLoader.getSystemClassLoader(), new Object[]{url});
            }
    
            Class start  = Class.forName(args[2]);
            Method main = start.getMethod("main", String[].class);
    
            String[] mainArgs = new String[0];
            if(args.length > 3){
                mainArgs = Arrays.copyOfRange(args, 3, args.length);
            }
            main.invoke(null,(Object)mainArgs);
        }
    
        private static URL[] buildURLs(String path, String jarNames) throws MalformedURLException {
            path = "http://" + path;
            String[] loadJars = jarNames.split(";");
    
            URL[] urls = new URL[loadJars.length];
    
            for(int i = 0;i < loadJars.length; ++i){
                urls[i] = new URL(path + "/" + loadJars[i]);
            }
    
            return urls;
        }
    }
    

你可能感兴趣的:(编程语言)