Better

业精于勤荒于嬉

Java反射12-动态加载和重加载类

Better's Avatar 2017-02-28 Java译文

  1. 1. 类加载器
  2. 2. 类加载层次结构
  3. 3. 类加载
  4. 4. 动态加载类
  5. 5. 动态重加载类
  6. 6. 设计你的重加载类代码
  7. 7. 类的加载重加载列子
  • The ClassLoader
  • The ClassLoader Hierarchy
  • Class Loading
  • Dynamic Class Loading
  • Dynamic Class Reloading
  • Designing your Code for Class Reloading
  • ClassLoader Load / Reload Example

在程序运行时去加载和重加载类是可能的,虽然它不像我们所希望的那么直接。本文将介绍何时以及怎样去加载和重加载类。
你可能会疑问动态加载类功能到底是不是Java反射的一部分,或者说是Java平台核心的一部分。不管怎么说本文将沿着Java反射的轨迹来讲解。

类加载器

Java应用的所有的类通过某些java.lang.ClassLoader的子类来加载。因此动态的加载类也必须通过某些java.lang.ClassLoader的子类来完成。
当一个类被加载,所有他引用的相关类也被加载了。这种类加载模式以递归(recursively)方式发生,知道所有需要的类都被加载。这也许不会吧应用的所有类的加载了。不被引用的类是不会被加载的直到他们被引用。

类加载层次结构

类加载器被层次化的组织起来。当你创建了一个新的加载器ClassLoader,你必须提供他的父类ClassLoader。如果一个加载器ClassLoader加载一个类,那么它会先要求父类去加载这个类。如果父类加载器找不到这个类,子类加载器会尝试去加载这个类。

类加载

一个指定的类被加载器加载的步凑是:

  1. 检查类是否被加载。
  2. 如果没有被加载,叫父类去加载这个类。
  3. 如果父类不能加载这个类,子加载器尝试去加载这个类。

当你实现一个能够重新加载类的类加载器时,你将需要从这个序列改变(deviate)一点点。要重新加载的类不应该由父类加载器加载。更多的稍后再谈(More on that later.)。

动态加载类

动态加载类是很容易的。所有你需要做的就是获取ClassLoader并且调用他的loadClass()方法。例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
public class MainClass {

public static void main(String[] args){

ClassLoader classLoader = MainClass.class.getClassLoader();

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

动态重加载类

这个有一点点挑战。Java的类加载器在加载类的时候总是先会检查一个类是否已经被加载了。因此重新加载一个类通过Java预定义的ClassLoader是不可能的。所有你需要实现你自定义的ClassLoader。
即使是你自定义类加载器也是富有挑战的。每个被加载的类需要关联起来。这可以通过ClassLoader.resolve()方法实现。这个方法是final的,因此不能在你自定义的类加载器中重写。resolve()方法不会应许一个类被两次关联。因此每次你想要去重新加载一个类的时候,你必须指定一个新的你的自定义来加载器。这不是不可能的,但是在设计类重载时需要知道。

设计你的重加载类代码

正如前面说到的你不能通过一个类加载器加载已经被加载过的类。因此你必须通过另外的类加载器对象来再次加载这个类。但是这会带来一些新的挑战。
Java中的每个被类加载器加载过得类都通过类的独立名称来确认,比如(包名+类名)。这就意味着一个被A加载器加载的MyObject类,在被B加载器加载过后是不同的两个类。比如像下面的代码:

1
2
MyObject object = (MyObject)
myClassReloadingFactory.newInstance("com.jenkov.MyObject");

注意到MyObject类的对象object在代码中是怎么被引用的。这因为MyObject类是由加载此代码所驻留的类的相同类加载器加载。
如果myClassReloadingFactory对象重新加载MyObject类使用的是其他的类加载器,而不是像上面代码那样使用的同一个类加载器,你就不能将加载得到的类对象转换为当前的MyObject对象。只要两个不同的加载器加载了MyObject类,他们就被任务是两个不同的对象,即使他们有相同的独立类名称。如果你尝试将一个对象转换成另外一个对象会爆出ClassCastException异常。
可以解决这个限制,但是你必须以两种方式更改代码:

  1. 使用接口作为变量类型,只需重新加载实现类。
  2. 使用超类作为变量类型,只需重新加载子类。

代码如下:

1
2
MyObjectInterface object = (MyObjectInterface)
myClassReloadingFactory.newInstance("com.jenkov.MyObject");
1
2
MyObjectSuperclass object = (MyObjectSuperclass)
myClassReloadingFactory.newInstance("com.jenkov.MyObject");

如果在重新加载实现类或子类时不重新加载变量类型,即接口或超类,这两种方法都将工作。
为了使上面的情况工作,你当然需要实现你的类加载器的父类加载器去加载接口或超类。当你的类被询问去加载MyObject类的时候,它同时也会被询问去加载MyOnjectInterface接口,或者MyObjectSuperclass父类。类加载器必须将这些类的加载委托给包含接口或超类类型变量的类同一个类加载器。

类的加载重加载列子

上文有太多的描述。让我们看一个简单的例子。下面是一个简单的ClassLoader的子类。留意到他是怎么讲类加载委托给他的父类来加载,除了一个它自己想加载的类。如果一个类的加载被委托给了它的父类,那么这个类在后面就不能被加载了。记住,一个类只能被一个ClassLoader对象加载一次。
正如前文所述,这仅仅是个像你展示ClassLoader怎样运行的例子。它不是一个正式的模板对于你自己的类加载器。你自己的类加载器不要局限于加载一个类,应该是一系列你想要去加载的类。此外,你应该也不应该硬编码类路径(译:直接写一个类的全路径)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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的使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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类通过加载器加载。注意到他是怎么继承父类并实现接口的。这仅仅是一个例子。在你自己的代码中你仅仅只需要两个钟的一个-继承或者实现。

原文

This article was last updated on days ago, and the information described in the article may have changed.