自定义一个类加载器打印“Hello World!”
现有两个类Demo
和HelloWorld
,其源代码如下:
Demo.java:
package com.xxx;
public class Demo {
public void print() {
HelloWorld.foo();
// System.out.println(HelloWorld.class.getClassLoader());
}
}
HelloWorld.java:
package com.xxx;
class HelloWorld {
public static void foo() {
System.out.println("Hello World!");
}
}
可以看到Demo
和HelloWorld
隶属于同一个包,Demo
的print()
方法中调用了HelloWorld.foo()
从而打印出"Hello World!"。
现有一个Main
类,其结构如下:
// 没有包名
public class Main {
public static void main(String[] args) {
...
}
}
现在,我们的任务是在Main
类的main()
方法中打印"Hello World!"。
一般而言,我们只需要new Demo()
,然后调用print()
方法即可完成打印。
然而,使用new
操作符的前提是我们已经提前导入了com.xxx.Demo
,但是,对于我们的Main
程序而言,com.xxx.Demo
一开始并不存在,它很有可能是在程序运行期间从网络上的某处下载下来,又或者是愚蠢的人类拷贝过来的。因而它并没有参与Main.java的编译,所以我们写Main的源代码时并不能导入com.xxx.Demo
。
对于Main
类而言,我们只知道会有一个com.xxx.Demo
的print()
方法帮助完成打印任务,却并不知晓它的具体位置。
某一时刻,一股神秘力量将Demo
和HelloWorld
传送到了我们的电脑上,现在它们与Main.class
的位置如下:
.
|-- app
| `-- com
| `-- xxx
| |-- Demo.class
| `-- HelloWorld.class
`-- Main.class
但是此时,Main
已经在运行了,启动它时使用了java -cp "." Main
命令,其中的-cp "."
选项明确指定了:对于开发者编写的类,只会从当前(与Main.class同级)目录下查找。而Demo
和HelloWorld
却在app路径下,因此,Main程序的类加载器无法对它们加载。
怎么办呢?
我们可以将Main.java
写成下面的样子:
import java.io.File;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws Exception {
URLClassLoader urlClassLoader =
new URLClassLoader(new URL[]{new File("./app").toURI().toURL()});
Class<?> clazz = urlClassLoader.loadClass("com.xxx.Demo");
Object obj = clazz.newInstance();
Method print = clazz.getMethod("print");
print.invoke(obj);
}
}
在Main.java
中,我们自定义了类加载器urlClassLoader
,它将会从./app
目录下查找class文件。
因此,当使用urlClassLoader.loadClass("com.xxx.Demo")
时能够加载到Demo.class
并创建Class
对象。
接着,因为无法创建com.xxx.Demo
的引用,我们只能使用反射来调用print()
方法。
这样一来,我们便可以愉快输出“Hello World!”了。如果把Demo.java
中的注释去掉,还可以打印出HelloWorld
类的类加载器,如下所示:
[root@VM_0_6_centos]# java -cp "." Main
Hello World!
java.net.URLClassLoader@2a139a55
可以看到,HelloWorld
的类加载器已经不是AppClassLoader
了,而是java.net.URLClassLoader
。
吾生也有涯,而知也无涯。