自定义一个类加载器打印“Hello World!”

现有两个类DemoHelloWorld,其源代码如下:

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!");
    }   
}

可以看到DemoHelloWorld隶属于同一个包,Demoprint()方法中调用了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.Demoprint()方法帮助完成打印任务,却并不知晓它的具体位置。

某一时刻,一股神秘力量将DemoHelloWorld传送到了我们的电脑上,现在它们与Main.class的位置如下:

.
|-- app
|   `-- com
|       `-- xxx
|           |-- Demo.class
|           `-- HelloWorld.class
`-- Main.class

但是此时,Main已经在运行了,启动它时使用了java -cp "." Main命令,其中的-cp "."选项明确指定了:对于开发者编写的类,只会从当前(与Main.class同级)目录下查找。DemoHelloWorld却在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

posted @ 2020-05-06 20:39  SanjiApollo  阅读(291)  评论(0编辑  收藏  举报