简体   繁体   English

Java ClassLoader:两次加载同一个类

[英]Java ClassLoader: load same class twice

I have a ClassLoader which loads a class compiled by JavaCompiler from a source file.我有一个ClassLoader ,它从源文件加载JavaCompiler编译的类。 But when I change the source file, save it and recompile it, the ClassLoader still loads the first version of the class.但是当我更改源文件、保存并重新编译它时, ClassLoader仍然加载该类的第一个版本。

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

What am I missing?我错过了什么? like a newInstance or something?像一个 newInstance 什么的?

A classloader can't replace a class that already has been loaded.类加载器不能替换已经加载的类。 loadClass will return the reference of the existing Class instance. loadClass将返回现有Class实例的引用。

You'll have to instantiate a new classloader and use it to load the new class.您必须实例化一个新的类加载器并使用它来加载新类。 And then, if you want to "replace" the class, you'll have to throw this classloader away and create another new one.然后,如果你想“替换”这个类,你必须扔掉这个类加载器并创建另一个新的类加载器。


In response to your comment(s): do something like回应您的评论:做类似的事情

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

This classloader will use the "default delegation parent ClassLoader" and you have to take care, the class (identified by it fully qualified classname) has not been loaded and can't be loaded by that parent classloader.该类加载器将使用“默认委托父类加载器”,您必须注意,该类(由其完全限定的类名标识)尚未加载且无法由该父类加载器加载。 So the "pathToClassAsUrl" shouldn't be on the classpath!所以“pathToClassAsUrl”不应该在类路径上!

As it was stated before,正如之前所说,

  1. Each class loader remembers (caches) the classes that is has loaded before and won't reload it again - essentially each class loader defines a namespace.每个类加载器都会记住(缓存)之前加载过的类并且不会再次重新加载它——本质上每个类加载器都定义了一个命名空间。
  2. Child class loader delegates class loading to the parent class loader, ie子类加载器将类加载委托给父类加载器,即

Java 8 and before Java 8 及之前

Custom Class Loader(s) -> App Class Loader -> Extension Class Loader -> Bootstrap Class Loader自定义类加载器 -> 应用类加载器 -> 扩展类加载器 -> 引导类加载器

Java 9+爪哇 9+

Custom Class Loader(s) -> App Class Loader -> Platform Class Loader -> Bootstrap Class Loader.自定义类加载器 -> 应用类加载器 -> 平台类加载器 -> 引导类加载器。

From the above we can conclude that each Class object is identified by its fully qualified class name and the loader than defined it (also known as defined loader)从上面我们可以得出结论,每个 Class 对象都由其完全限定的类名和加载器来标识,而不是定义它(也称为已定义加载器)

From Javadocs :Javadocs

Every Class object contains a reference to the ClassLoader that defined it.每个 Class 对象都包含对定义它的 ClassLoader 的引用。

The method defineClass converts an array of bytes into an instance of class Class.方法defineClass 将字节数组转换为类Class 的实例。 Instances of this newly defined class can be created using Class.newInstance.可以使用 Class.newInstance 创建这个新定义的类的实例。

The simple solution to reload class is to either define new (for example UrlClassLoader ) or your own custom class loader.重新加载类的简单解决方案是定义新的(例如UrlClassLoader )或您自己的自定义类加载器。 For more complex scenario where you need to substitute class dynamic proxy mechanism can be utilized.对于需要替代类的更复杂的场景,可以使用动态代理机制。

Please see below simple solution I used for a similar problem to reload same class by defining custom class loader.请参阅下面的简单解决方案,我通过定义自定义类加载器来解决类似问题以重新加载相同的类。 The essence - override findClass method of the parent class loader and then load the class from bytes read from the filesystem.本质 - 覆盖父类加载器的findClass方法,然后从从文件系统读取的字节加载类。

  1. MyClassLoader - overrides findClass and executed defineClass 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 - experiment subject - ** IMPORTANT : Compile with javac and then remove SimpleClass.java from class path (or just rename it) Otherwise it will be loaded by System Class Loader due to class loading delegation mechanism.** from src/main/java 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 - subject interface : separating interface from implementation to compile and execute output from the subject. SimpleInterface - 主题接口:将接口与实现分离以编译和执行主题的输出。

package com.example.classloader;

public interface SimpleInterface {

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

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
    }
}

You have to load a new ClassLoader each time, or you have to give the class a different name each time and access it via an interface.每次都必须加载一个新的 ClassLoader,或者每次都必须为类指定不同的名称并通过接口访问它。

eg例如

interface MyWorker {
  public void work();
}

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

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

I think the problem might be more basic than what the other answers suggest.我认为这个问题可能比其他答案所暗示的更基本。 It is very possible that the class loader is loading a different file than what you think it is.很可能类加载器正在加载与您认为的文件不同的文件。 To test out this theory, delete the .class file (DO NOT recompile your .java source) and run your code.为了测试这个理论,删除 .class 文件(不要重新编译你的 .java 源代码)并运行你的代码。 You should get an exception.你应该得到一个例外。

If you do not get the exception, then obviously the class loader is loading a different .class file than the one you think it is.如果您没有收到异常,那么显然类加载器正在加载与您认为的不同的 .class 文件。 So search for the location of another .class file with the same name.因此,搜索另一个具有相同名称的 .class 文件的位置。 Delete that .class file and try again.删除该 .class 文件并重试。 Keep trying until you find the .class file that is actually being loaded.继续尝试,直到找到实际正在加载的 .class 文件。 Once you do that, you can recompile your code and manually put the class file in the correct directory.一旦你这样做了,你就可以重新编译你的代码并手动将类文件放在正确的目录中。

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

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