Java类加载器:加载同一个类两次

Java类加载器:加载同一个类两次,java,class,compiler-construction,Java,Class,Compiler Construction,我有一个ClassLoader,它从源文件加载由JavaCompiler编译的类。 但是当我更改源文件、保存它并重新编译它时,ClassLoader仍然加载该类的第一个版本 ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class<?> compiledClass = cl.loadClass(stringClass); ClassLoader cl=Thread.currentThre

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

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

我错过了什么?像一个新实例之类的?

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

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


回应你的评论:做如下事情

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

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

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

e、 g


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


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

正如前面所述

  • 每个类加载器都会记住(缓存)以前加载过的类,并且不会再次重新加载它——本质上每个类加载器都定义了一个名称空间
  • 子类装入器将类装入委托给父类装入器,即
  • Java8及之前的版本

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

    爪哇9+

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

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

    发件人:

    每个类对象都包含对 定义它

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

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

    请参阅下面我用于解决类似问题的简单解决方案,通过定义自定义类加载器来重新加载相同的类。 essence-重写父类装入器的
    findClass
    方法,然后从文件系统读取的字节中装入类

  • MyClassLoader-重写
    findClass
    并执行
    defineClass

  • SimpleInterface-subject接口:将接口与实现分离,以编译和执行来自主题的输出

  • 驱动程序-执行以测试

  • package com.example.classloader;
    导入java.lang.reflect.InvocationTargetException;
    公共类MyClassLoaderTest{
    私有静态最终字符串path=“src/main/java/com/example/classloader/SimpleClass.class”;
    私有静态最终字符串className=“com.example.classloader.SimpleClass”;
    publicstaticvoidmain(字符串[]args)抛出ClassNotFoundException、NoSuchMethodException、IllegalAccessException、,
    InvocationTargetException,InstanceionException{//Exception地狱由于反射,抱歉:)
    MyClassLoader classLoaderOne=新的MyClassLoader(路径);
    classOne=classLoaderOne.loadClass(类名);
    //我们需要使用反射来实例化对象,
    //否则,如果我们使用'new',该类将由系统类加载器加载
    简单接口对象一=
    (SimpleInterface)classOne.getDeclaredConstructor().newInstance();
    //尝试使用相同的类加载器重新加载相同的类
    classOne=classLoaderOne.loadClass(类名);
    SimpleInterface objectOneReloaded=(SimpleInterface)classOne.getDeclaredConstructor().newInstance();
    //新类加载器
    MyClassLoader classLoaderTwo=新的MyClassLoader(路径);
    Class Class2=classLoaderTwo.loadClass(类名称);
    SimpleInterface ObjectTwo=(SimpleInterface)classTwo.getDeclaredConstructor().newInstance();
    System.out.println(objectOne.getCount());//输出2-因为它是同一个实例
    System.out.println(objectOneReloaded.getCount());//输出2-因为它是同一个实例
    System.out.println(ObjectTwo.getCount());//输出1-因为它是一个独特的新实例
    }
    }
    
    我如何替换或删除方法中的类加载器?谢谢,我在这里找到了一个很好的解释:@Andreas\D如果我创建两个新的类加载器并尝试用它们加载同一个类(仍然没有加载),会怎么样?然后在JVM中会得到同一类的两个“实例”。这就是应用程序服务器的原因
    interface MyWorker {
      public void work();
    }
    
    class Worker1 implement MyWorker {
      public void work() { /* code */ }
    }
    
    class Worker2 implement MyWorker {
      public void work() { /* different code */ }
    }
    
     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");
                }
            }
        }
    
    package com.example.classloader;
    
    public class SimpleClassRenamed implements SimpleInterface {
    
        private static long count;
    
        public SimpleClassRenamed() {
            count++;
        }
    
        @Override
        public long getCount() {
            return count;
        }
    }
    
    package com.example.classloader;
    
    public interface SimpleInterface {
    
        long getCount();
    }
    
    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
        }
    }