繁体   English   中英

Java ClassLoader:两次加载同一个类

[英]Java ClassLoader: load same class twice

我有一个ClassLoader ,它从源文件加载JavaCompiler编译的类。 但是当我更改源文件、保存并重新编译它时, ClassLoader仍然加载该类的第一个版本。

   ClassLoader cl = Thread.currentThread().getContextClassLoader();
   Class<?> compiledClass = cl.loadClass(stringClass);

我错过了什么? 像一个 newInstance 什么的?

类加载器不能替换已经加载的类。 loadClass将返回现有Class实例的引用。

您必须实例化一个新的类加载器并使用它来加载新类。 然后,如果你想“替换”这个类,你必须扔掉这个类加载器并创建另一个新的类加载器。


回应您的评论:做类似的事情

ClassLoader cl = new UrlClassLoader(new URL[]{pathToClassAsUrl});
Class<?> compiledClass = cl.loadClass(stringClass);

该类加载器将使用“默认委托父类加载器”,您必须注意,该类(由其完全限定的类名标识)尚未加载且无法由该父类加载器加载。 所以“pathToClassAsUrl”不应该在类路径上!

正如之前所说,

  1. 每个类加载器都会记住(缓存)之前加载过的类并且不会再次重新加载它——本质上每个类加载器都定义了一个命名空间。
  2. 子类加载器将类加载委托给父类加载器,即

Java 8 及之前

自定义类加载器 -> 应用类加载器 -> 扩展类加载器 -> 引导类加载器

爪哇 9+

自定义类加载器 -> 应用类加载器 -> 平台类加载器 -> 引导类加载器。

从上面我们可以得出结论,每个 Class 对象都由其完全限定的类名和加载器来标识,而不是定义它(也称为已定义加载器)

Javadocs

每个 Class 对象都包含对定义它的 ClassLoader 的引用。

方法defineClass 将字节数组转换为类Class 的实例。 可以使用 Class.newInstance 创建这个新定义的类的实例。

重新加载类的简单解决方案是定义新的(例如UrlClassLoader )或您自己的自定义类加载器。 对于需要替代类的更复杂的场景,可以使用动态代理机制。

请参阅下面的简单解决方案,我通过定义自定义类加载器来解决类似问题以重新加载相同的类。 本质 - 覆盖父类加载器的findClass方法,然后从从文件系统读取的字节加载类。

  1. MyClassLoader - 覆盖findClass并执行defineClass

 package com.example.classloader;
    
    import java.io.IOException;
    import java.nio.file.Files;
    import java.nio.file.Paths;
    
    public class MyClassLoader extends ClassLoader {
    
        private String classFileLocation;
    
        public MyClassLoader(String classFileLocation) {
            this.classFileLocation = classFileLocation;
        }
    
        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classBytes = loadClassBytesFromDisk(classFileLocation);
            return defineClass(name, classBytes, 0, classBytes.length);
        }
    
        private byte []  loadClassBytesFromDisk(String classFileLocation) {
            try {
                return Files.readAllBytes(Paths.get(classFileLocation));
            }
            catch (IOException e) {
                throw new RuntimeException("Unable to read file from disk");
            }
        }
    }
  1. SimpleClass - 实验主题 - ** 重要:用javac ,然后从类路径中删除 SimpleClass.java(或只是重命名)否则,由于类加载委托机制,它将被系统类加载器加载。**来自src/main/java

javac com/example/classloader/SimpleClass.java


package com.example.classloader;

public class SimpleClassRenamed implements SimpleInterface {

    private static long count;

    public SimpleClassRenamed() {
        count++;
    }

    @Override
    public long getCount() {
        return count;
    }
}
  1. SimpleInterface - 主题接口:将接口与实现分离以编译和执行主题的输出。

package com.example.classloader;

public interface SimpleInterface {

    long getCount();
}
  1. 驱动程序 - 执行以测试

package com.example.classloader;

import java.lang.reflect.InvocationTargetException;

public class MyClassLoaderTest {

    private static final String path = "src/main/java/com/example/classloader/SimpleClass.class";
    private static final String className = "com.example.classloader.SimpleClass";

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException,
            InvocationTargetException, InstantiationException {  // Exception hell due to reflection, sorry :)
        MyClassLoader classLoaderOne = new MyClassLoader(path);
        Class<?> classOne = classLoaderOne.loadClass(className);

        // we need to instantiate object using reflection,
        // otherwise if we use `new` the Class will be loaded by the System Class Loader
        SimpleInterface objectOne =
                (SimpleInterface) classOne.getDeclaredConstructor().newInstance();


        // trying to re-load the same class using same class loader
        classOne = classLoaderOne.loadClass(className); 
        SimpleInterface objectOneReloaded = (SimpleInterface) classOne.getDeclaredConstructor().newInstance();

        // new class loader
        MyClassLoader classLoaderTwo = new MyClassLoader(path);
        Class<?> classTwo = classLoaderTwo.loadClass(className);
        SimpleInterface ObjectTwo = (SimpleInterface) classTwo.getDeclaredConstructor().newInstance();

        System.out.println(objectOne.getCount()); // Outputs 2 - as it is the same instance
        System.out.println(objectOneReloaded.getCount()); // Outputs 2 - as it is the same instance

        System.out.println(ObjectTwo.getCount()); // Outputs 1 - as it is a distinct new instance
    }
}

每次都必须加载一个新的 ClassLoader,或者每次都必须为类指定不同的名称并通过接口访问它。

例如

interface MyWorker {
  public void work();
}

class Worker1 implement MyWorker {
  public void work() { /* code */ }
}

class Worker2 implement MyWorker {
  public void work() { /* different code */ }
}

我认为这个问题可能比其他答案所暗示的更基本。 很可能类加载器正在加载与您认为的文件不同的文件。 为了测试这个理论,删除 .class 文件(不要重新编译你的 .java 源代码)并运行你的代码。 你应该得到一个例外。

如果您没有收到异常,那么显然类加载器正在加载与您认为的不同的 .class 文件。 因此,搜索另一个具有相同名称的 .class 文件的位置。 删除该 .class 文件并重试。 继续尝试,直到找到实际正在加载的 .class 文件。 一旦你这样做了,你就可以重新编译你的代码并手动将类文件放在正确的目录中。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM