Java Reflection-动态类加载和重新加载

时间:2020-01-09 10:36:19  来源:igfitidea点击:

可以在Java运行时加载和重新加载类,尽管它并不像人们希望的那样简单。本文将说明何时以及如何加载和重新加载Java类。

我们可以争论Java的动态类加载函数是否确实是Java Reflection的一部分,还是核心Java平台的一部分。无论如何,这篇文章被放在Java Reflection的踪迹中,缺少一个更好的放置位置。

ClassLoader

Java应用程序中的所有类都使用java.lang.ClassLoader的某些子类加载。因此,还必须使用java.lang.ClassLoader子类来动态加载类。

当加载一个类时,它所引用的所有类也都被加载。该类加载模式是递归发生的,直到所有需要的类都加载完为止。这可能不是应用程序中的所有类。未引用的类在被引用之前不会被加载。

ClassLoader层次结构

Java中的类加载器被组织成一个层次结构。创建新的标准JavaClassLoader时,必须为其提供父ClassLoader。如果要求ClassLoader加载类,它将要求其父类加载器加载它。如果父类加载器找不到该类,则子类加载器将尝试自行加载它。

类加载

给定的类加载器在加载类时使用的步骤是:

  • 检查该类是否已经加载。
  • 如果未加载,请要求父类加载器加载该类。
  • 如果父类加载器无法加载类,请尝试在该类加载器中加载它。

当实现能够重新加载类的类加载器时,我们将需要从此序列中稍微偏离一点。父类加载器不应请求重新加载的类。以后再说。

动态类加载

动态加载类很容易。我们需要做的就是获取" ClassLoader"并调用其" loadClass()"方法。这是一个例子:

public class MainClass {

  public static void main(String[] args){

    ClassLoader classLoader = MainClass.class.getClassLoader();

    try {
        Class aClass = classLoader.loadClass("com.Hyman.MyClass");
        System.out.println("aClass.getName() = " + aClass.getName());
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    }

}

动态类重载

动态类重载更具挑战性。 Java的内置类加载器总是在加载类之前检查是否已经加载了一个类。因此,无法使用Java的内置类加载器重新加载该类。要重新加载一个类,我们必须实现自己的ClassLoader子类。

即使使用ClassLoader的自定义子类,我们也面临挑战。每个加载的类都需要链接。这是通过使用ClassLoader.resolve()方法完成的。这个方法是最终的,因此不能在ClassLoader子类中重写。 resolve()方法将不允许任何给定的ClassLoader实例两次将同一类链接两次。因此,每次要重新加载类时,都必须使用ClassLoader子类的新实例。这不是不可能,但在设计类重载时必须知道。

设计代码以进行类重载

如前所述,我们不能使用已经加载过该类的ClassLoader重新加载该类。因此,我们将不得不使用另一个ClassLoader实例重新加载该类。但这带来了新的挑战。

Java应用程序中加载的每个类都由其完全限定名称(包名称+类名称)以及加载它的ClassLoader实例来标识。这意味着,由类加载器A加载的类MyObject与由类加载器B加载的MyObject类不同。请看以下代码:

MyObject object = (MyObject)
    myClassReloadingFactory.newInstance("com.Hyman.MyObject");

注意在代码中如何引用MyObject类,作为object变量的类型。这将导致MyObject类由与加载此代码所在类的类相同的类加载器加载。

如果myClassReloadingFactory对象工厂使用与上面代码所在的类不同的类加载器重新加载MyObject类,则无法将重新加载的MyObject类的实例转换为object的MyObject类型。多变的。由于两个MyObject类使用不同的类加载器加载,因此即使它们具有相同的完全限定的类名称,也将它们视为不同的类。试图将一个类的对象强制转换为另一个类的引用将导致" ClassCastException"。

可以解决此限制,但是我们将不得不通过以下两种方式之一来更改代码:

  • 使用接口作为变量类型,然后重新加载实现类。
  • 使用超类作为变量类型,然后重新加载子类。

这是两个对应的代码示例:

MyObjectInterface object = (MyObjectInterface)
    myClassReloadingFactory.newInstance("com.Hyman.MyObject");
MyObjectSuperclass object = (MyObjectSuperclass)
    myClassReloadingFactory.newInstance("com.Hyman.MyObject");

如果在重载实现类或者子类时未重载变量的类型(接口或者超类),则这两种方法都将起作用。

为了完成这项工作,我们当然需要实现类加载器,以使接口或者超类由其父级加载。当类加载器被要求加载MyObject类时,还将要求其加载MyObjectInterface类或者MyObjectSuperclass类,因为它们是从MyObject类中引用的。类加载器必须将这些类的加载委托给加载了包含接口或者超类类型变量的类的同一类加载器。

ClassLoader加载/重新加载示例

上面的文字包含了很多话题。让我们看一个简单的例子。下面是一个简单的ClassLoader子类的示例。请注意,它将如何将类加载委派给其父级,但打算重新加载的是一个类。如果此类的加载委托给父类加载器,则以后无法重新加载。请记住,一个类只能由同一ClassLoader实例加载一次。

如前所述,这只是一个示例,用于向我们展示ClassLoader行为的基础知识。它不是我们自己的类加载器的生产就绪模板。我们自己的类加载器可能不应该局限于单个类,而应该知道我们需要重新加载的类的集合。另外,我们可能也不应该对类路径进行硬编码。

public class MyClassLoader extends ClassLoader{

    public MyClassLoader(ClassLoader parent) {
        super(parent);
    }

    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if(!"reflection.MyObject".equals(name))
                return super.loadClass(name);

        try {
            String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
                            "classes/reflection/MyObject.class";
            URL myUrl = new URL(url);
            URLConnection connection = myUrl.openConnection();
            InputStream input = connection.getInputStream();
            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
            int data = input.read();

            while(data != -1){
                buffer.write(data);
                data = input.read();
            }

            input.close();

            byte[] classData = buffer.toByteArray();

            return defineClass("reflection.MyObject",
                    classData, 0, classData.length);

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return null;
    }

}

以下是MyClassLoader的使用示例。

public static void main(String[] args) throws
    ClassNotFoundException,
    IllegalAccessException,
    InstantiationException {

    ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
    MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
    Class myObjectClass = classLoader.loadClass("reflection.MyObject");

    AnInterface2       object1 =
            (AnInterface2) myObjectClass.newInstance();

    MyObjectSuperClass object2 =
            (MyObjectSuperClass) myObjectClass.newInstance();

    //create new class loader so classes can be reloaded.
    classLoader = new MyClassLoader(parentClassLoader);
    myObjectClass = classLoader.loadClass("reflection.MyObject");

    object1 = (AnInterface2)       myObjectClass.newInstance();
    object2 = (MyObjectSuperClass) myObjectClass.newInstance();

}

这是使用类加载器加载的reflection.MyObject类。请注意,它既扩展了超类又实现了接口。仅出于示例的目的。在我们自己的代码中,我们只需要扩展或者实现这两者之一即可。

public class MyObject extends MyObjectSuperClass implements AnInterface2{
    //... body of class ... override superclass methods
    //    or implement interface methods
}