Java ClassLoader是项目开发中最重要的但很少使用的组件之一,我从未在任何一个项目中扩展过ClassLoader,但是,拥有我自己的ClassLoader可以定制Java类加载的想法令人兴奋,本文将提供Java ClassLoader的概述,然后继续在Java中创建个性化类加载器。
Java ClassLoader是什么?
我们知道 Java 程序在 Java 虚拟机(JVM)上运行(JVM)。当我们编译一个 Java 类时,JVM 会创建字体代码,该字体代码是平台和机器独立的。
内置 ClassLoader 类型
在Java中有三种类型的内置 ClassLoader。
- Bootstrap Class Loader – 它加载了 JDK 内部类,加载了 rt.jar 和其他核心类,例如 java.lang.* 包类
- Extensions Class Loader – 它从 JDK 扩展目录中加载了类,通常是 $JAVA_HOME/lib/ext 目录
- System Class Loader – 这个类加载器从当前的 classpath 加载了类。
阶级层次结构
ClassLoader在将一类加载到内存中具有层次性,每当提出请求来加载一类时,它会将其授权给母类加载器。这就是如何在运行时环境中保持独特性。如果母类加载器无法找到该类,那么类加载器本身就会试图加载该类。
1package com.journaldev.classloader;
2
3public class ClassLoaderTest {
4
5 public static void main(String[] args) {
6
7 System.out.println("class loader for HashMap: "
8 + java.util.HashMap.class.getClassLoader());
9 System.out.println("class loader for DNSNameService: "
10 + sun.net.spi.nameservice.dns.DNSNameService.class
11 .getClassLoader());
12 System.out.println("class loader for this class: "
13 + ClassLoaderTest.class.getClassLoader());
14
15 System.out.println(com.mysql.jdbc.Blob.class.getClassLoader());
16
17 }
18
19}
输出:
1class loader for HashMap: null
2class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093
3class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37
4sun.misc.Launcher$AppClassLoader@64cbbe37
Java ClassLoader是如何工作的?
让我们从上面的程序输出中了解类加载器的工作。
- java.util.HashMap ClassLoader的出现是无效的,这反映了Bootstrap ClassLoader. DNSName Service classLoader 是 ExtClassLoader 的分类。 由于类本身在CLASSPATH中,系统类Loader会加载.
- 当我们试图加载HashMap时,我们的系统类Loader将它委托给扩展类Loader. 扩展级装载机将它委托给"靴子"级Loader. 靴子级装载器找到HashMap级并装入JVM内存. (_ ( )* DNSNamesService类也遵循同样的流程. 但"靴子"类Loader无法定位,因为它位于"$JAVA_HOME/lib/ext/dns.jar". 因此,它会被扩展类装载器加载。 () ( )* Blob 类包含在 MySql [JDBC] (/community/tourises/jdbc-tutorial)中. 接通器罐(mysql-connector-java- 5.0.7-bin.jar),存在于项目建设路径. 它也被系统级装载器装入. ( ( )* 儿童班上装载器所装载的班次,可视同其母班上装载器所装载的班次. 因此,系统类装填器所装载的类具有能见度被扩展和靴子类装填器所装载的类. (_ ( )* 如果有兄弟级加载器,他们就不能进入相互加载的班级。 ( (英语)
为什么要用Java编写自定义 ClassLoader?
Java 默认 ClassLoader 可以从本地文件系统上加载类,这对于大多数情况来说足够好,但是,如果您在加载类时,预计在运行时或从 FTP 服务器或通过第三方 Web 服务上加载类,那么您必须扩展现有类加载器。
Java ClassLoader 方法
- 当 JVM 请求一个类时,它会通过传递该类的完全分类名称来调用 ClassLoader 的 `loadClass() 函数
- loadClass() 函数会调用
findLoadedClass()
方法来检查该类是否已被加载。
Java 定制 ClassLoader 示例
We will create our own ClassLoader by extending the ClassLoader class and overriding the loadClass(String name) method. If the class name will start from com.journaldev
then we will load it using our custom class loader or else we will invoke the parent ClassLoader loadClass()
method to load the class.
主页:Java
这是我们的自定义类型加载器,下面的方法。
private byte[] loadClassFileData(String name)
:此方法将从文件系统读取类文件到 byte arrayprivate Class<?> getClass(String name)
:此方法将调用 loadClassFileData()函数,并通过调用 parent defineClass() 方法,将生成 Class并返回它public Class?> loadClass(String name)
:此方法负责加载类。
1import java.io.DataInputStream;
2import java.io.File;
3import java.io.IOException;
4import java.io.InputStream;
5
6/**
7 * Our Custom ClassLoader to load the classes. Any class in the com.journaldev
8 * package will be loaded using this ClassLoader. For other classes, it will delegate the request to its Parent ClassLoader.
9 *
10 */
11public class CCLoader extends ClassLoader {
12
13 /**
14 * This constructor is used to set the parent ClassLoader
15 */
16 public CCLoader(ClassLoader parent) {
17 super(parent);
18 }
19
20 /**
21 * Loads the class from the file system. The class file should be located in
22 * the file system. The name should be relative to get the file location
23 *
24 * @param name
25 * Fully Classified name of the class, for example, com.journaldev.Foo
26 */
27 private Class getClass(String name) throws ClassNotFoundException {
28 String file = name.replace('.', File.separatorChar) + ".class";
29 byte[] b = null;
30 try {
31 // This loads the byte code data from the file
32 b = loadClassFileData(file);
33 // defineClass is inherited from the ClassLoader class
34 // that converts byte array into a Class. defineClass is Final
35 // so we cannot override it
36 Class c = defineClass(name, b, 0, b.length);
37 resolveClass(c);
38 return c;
39 } catch (IOException e) {
40 e.printStackTrace();
41 return null;
42 }
43 }
44
45 /**
46 * Every request for a class passes through this method. If the class is in
47 * com.journaldev package, we will use this classloader or else delegate the
48 * request to parent classloader.
49 *
50 *
51 * @param name
52 * Full class name
53 */
54 @Override
55 public Class loadClass(String name) throws ClassNotFoundException {
56 System.out.println("Loading Class '" + name + "'");
57 if (name.startsWith("com.journaldev")) {
58 System.out.println("Loading Class using CCLoader");
59 return getClass(name);
60 }
61 return super.loadClass(name);
62 }
63
64 /**
65 * Reads the file (.class) into a byte array. The file should be
66 * accessible as a resource and make sure that it's not in Classpath to avoid
67 * any confusion.
68 *
69 * @param name
70 * Filename
71 * @return Byte array read from the file
72 * @throws IOException
73 * if an exception comes in reading the file
74 */
75 private byte[] loadClassFileData(String name) throws IOException {
76 InputStream stream = getClass().getClassLoader().getResourceAsStream(
77 name);
78 int size = stream.available();
79 byte buff[] = new byte[size];
80 DataInputStream in = new DataInputStream(stream);
81 in.readFully(buff);
82 in.close();
83 return buff;
84 }
85}
主页:Java
这是我们用 主功能的测试类。我们正在创建我们的 ClassLoader 实例并使用其 loadClass() 方法加载样本类。加载类后,我们正在使用 Java Reflection API来调用其方法。
1import java.lang.reflect.Method;
2
3public class CCRun {
4
5 public static void main(String args[]) throws Exception {
6 String progClass = args[0];
7 String progArgs[] = new String[args.length - 1];
8 System.arraycopy(args, 1, progArgs, 0, progArgs.length);
9
10 CCLoader ccl = new CCLoader(CCRun.class.getClassLoader());
11 Class clas = ccl.loadClass(progClass);
12 Class mainArgType[] = { (new String[0]).getClass() };
13 Method main = clas.getMethod("main", mainArgType);
14 Object argsArray[] = { progArgs };
15 main.invoke(null, argsArray);
16
17 // Below method is used to check that the Foo is getting loaded
18 // by our custom class loader i.e CCLoader
19 Method printCL = clas.getMethod("printCL", null);
20 printCL.invoke(null, new Object[0]);
21 }
22
23}
3、Foo.java 和 Bar.java
这些是我们的测试类,由我们的自定义类加载器加载。它们有一个printCL()
方法,它被调用来打印 ClassLoader 信息。 Foo 类将由我们的自定义类加载器加载。 Foo 使用 Bar 类,所以 Bar 类也将由我们的自定义类加载器加载。
1package com.journaldev.cl;
2
3public class Foo {
4 static public void main(String args[]) throws Exception {
5 System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]);
6 Bar bar = new Bar(args[0], args[1]);
7 bar.printCL();
8 }
9
10 public static void printCL() {
11 System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader());
12 }
13}
1package com.journaldev.cl;
2
3public class Bar {
4
5 public Bar(String a, String b) {
6 System.out.println("Bar Constructor >>> " + a + " " + b);
7 }
8
9 public void printCL() {
10 System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader());
11 }
12}
Java Custom ClassLoader 执行步骤
首先,我们将通过命令行编译所有类。之后,我们将通过三种参数来运行 CCRun 类。 第一种参数是由我们的类加载器加载的 Foo 类的完全分类名称。 另外两个参数被传递到 Foo 类主函数和 Bar 构建器。 执行步骤和输出将如下。
1$ javac -cp . com/journaldev/cl/Foo.java
2$ javac -cp . com/journaldev/cl/Bar.java
3$ javac CCLoader.java
4$ javac CCRun.java
5CCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter;
6cast to java.lang.Class<?> for a varargs call
7cast to java.lang.Class<?>[] for a non-varargs call and to suppress this warning
8Method printCL = clas.getMethod("printCL", null);
9^
101 warning
11$ java CCRun com.journaldev.cl.Foo 1212 1313
12Loading Class 'com.journaldev.cl.Foo'
13Loading Class using CCLoader
14Loading Class 'java.lang.Object'
15Loading Class 'java.lang.String'
16Loading Class 'java.lang.Exception'
17Loading Class 'java.lang.System'
18Loading Class 'java.lang.StringBuilder'
19Loading Class 'java.io.PrintStream'
20Foo Constructor >>> 1212 1313
21Loading Class 'com.journaldev.cl.Bar'
22Loading Class using CCLoader
23Bar Constructor >>> 1212 1313
24Loading Class 'java.lang.Class'
25Bar ClassLoader: CCLoader@71f6f0bf
26Foo ClassLoader: CCLoader@71f6f0bf
27$
如果你看看输出,它正在试图加载com.journaldev.cl.Foo
类,因为它正在扩展java.lang.Object类,它正在试图先加载Object类,所以请求来自CCLoader loadClass方法,它正在委托它到母类类,所以母类加载器正在加载Object, String和其他Java类。我们的ClassLoader只是从文件系统中加载Foo和Bar类。从 printCL()函数的输出中很清楚。我们可以更改 loadClassFileData()功能,从FTP服务器或呼吁任何第三方服务来读取字节数,以便在飞行中获取字节数组。我希望这篇文章对理解JavaLoader Class工作有用,以及我们如何扩展它来做更多的事情,而
定制 ClassLoader 作为默认 ClassLoader
当 JVM 启动时,我们可以通过使用 Java 选项将我们的自定义类加载器作为默认的加载器。
1$ javac -cp .:../lib/mysql-connector-java-5.0.7-bin.jar com/journaldev/classloader/ClassLoaderTest.java
2$ java -cp .:../lib/mysql-connector-java-5.0.7-bin.jar -Djava.system.class.loader=CCLoader com.journaldev.classloader.ClassLoaderTest
3Loading Class 'com.journaldev.classloader.ClassLoaderTest'
4Loading Class using CCLoader
5Loading Class 'java.lang.Object'
6Loading Class 'java.lang.String'
7Loading Class 'java.lang.System'
8Loading Class 'java.lang.StringBuilder'
9Loading Class 'java.util.HashMap'
10Loading Class 'java.lang.Class'
11Loading Class 'java.io.PrintStream'
12class loader for HashMap: null
13Loading Class 'sun.net.spi.nameservice.dns.DNSNameService'
14class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457
15class loader for this class: CCLoader@38503429
16Loading Class 'com.mysql.jdbc.Blob'
17sun.misc.Launcher$AppClassLoader@2f94ca6c
18$
CCLoader 正在加载 ClassLoaderTest 类,因为它位于 com.journaldev
包中。
您可以从我们的 GitHub 存储库下载 ClassLoader 示例代码。