【问题标题】:Recursive ClassLoader in JavaJava中的递归类加载器
【发布时间】:2013-12-06 08:52:32
【问题描述】:

我需要定义一个ClassLoader,它可以从它们各自的类文件中动态地重新加载某些类。原因是类在运行时开始后发生了变化。

目前我有一个类加载器,可以从其文件中加载一个类。我需要进一步加载这个类所依赖的所有类,并且这些类都在更改的类列表中。

简单解释,我有一个ClassLoader 类,它覆盖loadClass 方法以在所需的类在列表中时加载更改的类文件。否则,它使用默认(父)类加载器加载类。

是否也可以使用相同的类加载器从主要加载的类(重新)加载类定义中的类?

所以,一个简单的例子

Class A {
    static method() {
        B b = new B();
        C c = new C();
        // do something with B and C
    }
}
Class B {
    public X x = new X();
}

当我用MyCustomClassLoader 加载类A 时,我给它一个类列表,其中包含AC f.e.因为我知道这些类的类文件(bin/A.class)已更改。因此,它应该从类A 手动加载文件,并且在加载BC 时,它应该检查它们是否在更改的类列表中。当它们不是时,可以使用默认的类加载器加载它们,否则它将与 A 一样,递归地(因此还要检查 B 所依赖的类)。

我可以从文件加载,但没有递归。我也知道此方法不会全局覆盖 Java new 运算符。但我只需要运行一个更改的类,但它的依赖项也发生了变化。

编辑: 请注意,使用Instrumentation 无法实现这一点。它的文档引用了以下内容,包括retransformClasses()redefineClasses()

重新转换可能会改变方法体、常量池和属性。重新转换不得添加、删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能会在未来的版本中取消。

所以我做了一个工作代理来更改方法体,但只有当我注意到它在方法创建或删除时崩溃时,我才发现了这段文字。

【问题讨论】:

  • 我不确定在运行中彻底替换一个类有多可行。加载一个,当然……因为 JVM 还没有将它合并到任何东西中。但是一旦加载,就会做出假设,JITter 会根据这些假设生成代码。最大的问题是,旧类的现有实例应该如何处理?
  • 嗯,不可能替换运行时类定义,这是肯定的。但我只是想在一个类加载器中重新定义类。这不会改变运行时环境中其他地方 new 运算符的行为,而只会改变使用我自己的类加载器创建的该类的实例的行为。
  • 可能的,请参阅下面的答案。但是,请参阅有关先前定义的类的现有实例的注意事项。
  • 可以替换运行时方法实现(主体),但不能使用 Instrumentation 替换类或方法的定义(签名)。

标签: java class classloader


【解决方案1】:

我终于让它工作了。我做了以下课程:

我相信这里的关键是resolveClass() 方法。虽然文档没有提供任何有用的信息,但我相信这种方法可以确保在加载一个类之后,所有使用的类也会被加载。

每次更改的班级再次更改或新班级更改时,您都需要创建一个新班级。

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Set;

/**
 * This class provides dynamic loading in the sense that the given class and 
 * other changed classes are loaded from disk, not from cache.
 * Ensures most recent version is used for those classes.
 */
public class DynamicClassLoader extends ClassLoader{

    private Set<String> classes;

    public DynamicClassLoader(Set<String> changedClasses) {
        this.classes = changedClasses;
    }

    /**
     * Dynamically loads the class from it's binary file. 
     * Classes this class depends on will only be loaded dynamically when 
     * they are in list of changed classes given at construction.
     * @throws ClassNotFoundException 
     */
    public Class<?> dynamicallyLoadClass(String name) 
        throws ClassNotFoundException {
        classes.add(name);
        return loadClass(name);
    }

    /**
     * Finds the class dynamically, contrary to Class.forname which can use 
     * cached copies.
     * This means it forces a reload of the class data.
     * Source: http://stackoverflow.com/questions/3971534/
     */
    @Override
    protected Class<?> findClass(String s) throws ClassNotFoundException {
        try {
            byte[] bytes;
            bytes = loadClassData(s);
            return defineClass(s, bytes, 0, bytes.length);
        } catch (IOException e) {
            e.printStackTrace();
            throw new ClassNotFoundException();
        }
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException{
        return loadClass(name, true);
    }

    /**
     * Overridden to not check the parent's loadClass method first.
     */
    @Override
    protected Class<?> loadClass(String name, boolean resolve) 
        throws ClassNotFoundException {
        Class<?> clazz = findLoadedClass(name);
        if(clazz != null)
            return clazz;

        if(classes.contains(name)) {
            clazz = findClass(name);
            resolveClass(clazz);
            return clazz;
        }
        return super.loadClass(name, resolve);
    }

    /**
     * Load class data from byte code.
     * 
     * @param className
     * @return
     * @throws IOException
     */
    protected byte[] loadClassData(String className) throws IOException {
        File f = new File("bin/" 
            + className.replaceAll("\\.", "/") + ".class");
        int size = (int) f.length();
        byte buff[] = new byte[size];
        FileInputStream fis = new FileInputStream(f);
        DataInputStream dis = new DataInputStream(fis);
        dis.readFully(buff);
        dis.close();
        return buff;
    }
}

【讨论】:

    【解决方案2】:

    我强烈建议您使用java.lang.instrument 来完成您想要完成的任务,而不是使用类加载器。具体来说,通过实现ClassFileTransformer 接口,注册代理,并使用注册期间传递给代理的Instrumentation 实例的redefineClasses() 和/或retransformClasses() 方法。

    请注意,当您重新定义一个类时,之前的类定义的现有实例不会受到影响。无论使用自定义 ClassLoaders 还是检测工具,都无法解决这个问题。

    This page in particular 你会发现关于如何使用仪器来完成你想做的事情的信息非常丰富。

    【讨论】:

    • 我用代理试过了,它部分工作。然后我在 Instrumentation javadoc 中读到这一行:“重新转换可能会更改方法体、常量池和属性。重新转换不得添加、删除或重命名字段或方法,更改方法的签名或更改继承。这些限制可能在未来的版本中被取消。”我也需要对方法创建和删除的支持。
    • 那是我最好的镜头。在这种情况下,我认为您尝试完成的任务是不可能的,无论是通过检测还是类加载器。
    • 好吧,我终于让它工作了(就在我看到你的评论之前)。您说得对,无法替换运行时对类的定义,但是您可以重新加载该类以获取一个类对象,您可以使用该对象创建新类的新实例。
    猜你喜欢
    • 2020-10-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-13
    相关资源
    最近更新 更多