Java ClassLoader 短命女 2023-02-28 06:11 1阅读 0赞 Java ClassLoader is one of the crucial but rarely used components in project development. I have never extended ClassLoader in any of my projects. But, the idea of having my own ClassLoader that can customize the Java class loading is exciting. Java ClassLoader是项目开发中至关重要但很少使用的组件之一。 我从未在任何项目中扩展ClassLoader。 但是,拥有自己的可以自定义Java类加载的ClassLoader的想法令人兴奋。 This article will provide an overview of Java ClassLoader and then move forward to create a custom class loader in Java. 本文将概述Java ClassLoader,然后继续使用Java创建自定义类加载器。 ## 什么是Java ClassLoader? **(****What is Java ClassLoader?****)** ## We know that Java Program runs on [Java Virtual Machine][] (JVM). When we compile a Java Class, JVM creates the bytecode, which is platform and machine-independent. The bytecode is stored in a **.class file**. When we try to use a class, the ClassLoader loads it into the memory. 我们知道Java程序在[Java虚拟机][Java Virtual Machine] (JVM)上运行。 当我们编译Java类时,JVM将创建字节码,该字节码与平台和机器无关。 字节码存储在**.class文件中** 。 当我们尝试使用一个类时,ClassLoader将其加载到内存中。 ## 内置的ClassLoader类型 **(****Built-in ClassLoader Types****)** ## There are three types of built-in ClassLoader in Java. Java内置了三种类型的内置ClassLoader。 1. **Bootstrap Class Loader** – It loads JDK internal classes. It loads rt.jar and other core classes for example java.lang.\* package classes. **Bootstrap类加载器** –加载JDK内部类。 它加载rt.jar和其他核心类,例如java.lang。\*包类。 2. **Extensions Class Loader** – It loads classes from the JDK extensions directory, usually $JAVA\_HOME/lib/ext directory. **扩展类加载器** –它从JDK扩展目录(通常为$ JAVA\_HOME / lib / ext目录)中加载类。 3. **System Class Loader** – This classloader loads classes from the current classpath. We can set classpath while invoking a program using -cp or -classpath command line option. **系统类加载器** – **该类加载**器从当前类路径加载类。 我们可以在使用-cp或-classpath命令行选项调用程序时设置classpath。 ## ClassLoader层次结构 **(****ClassLoader Hierarchy****)** ## ClassLoader is hierarchical in loading a class into memory. Whenever a request is raised to load a class, it delegates it to the parent classloader. This is how uniqueness is maintained in the runtime environment. If the parent class loader doesn’t find the class then the class loader itself tries to load the class. ClassLoader在将类加载到内存中是分层的。 每当提出加载类的请求时,它都会将其委托给父类加载器。 这就是在运行时环境中保持唯一性的方式。 如果父类加载器找不到该类,则该类加载器本身将尝试加载该类。 Let’s understand this by executing the below java program. 让我们通过执行以下java程序来了解这一点。 package com.journaldev.classloader; public class ClassLoaderTest { public static void main(String[] args) { System.out.println("class loader for HashMap: " + java.util.HashMap.class.getClassLoader()); System.out.println("class loader for DNSNameService: " + sun.net.spi.nameservice.dns.DNSNameService.class .getClassLoader()); System.out.println("class loader for this class: " + ClassLoaderTest.class.getClassLoader()); System.out.println(com.mysql.jdbc.Blob.class.getClassLoader()); } } Output: 输出: class loader for HashMap: null class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093 class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37 sun.misc.Launcher$AppClassLoader@64cbbe37 ## Java ClassLoader如何工作? **(****How Java ClassLoader Works?****)** ## Let’s understand the working of class loaders from the above program output. 让我们从上面的程序输出中了解类加载器的工作方式。 * The [java.util.HashMap][] ClassLoader is coming as null, which reflects Bootstrap ClassLoader. The DNSNameService class ClassLoader is ExtClassLoader. Since the class itself is in CLASSPATH, System ClassLoader loads it. [java.util.HashMap][] ClassLoader以null形式出现,这反映了Bootstrap ClassLoader。 DNSNameService类ClassLoader是ExtClassLoader。 由于类本身位于CLASSPATH中,因此System ClassLoader会加载它。 * When we are trying to load HashMap, our System ClassLoader delegates it to the Extension ClassLoader. The extension class loader delegates it to the Bootstrap ClassLoader. The bootstrap class loader finds the HashMap class and loads it into the JVM memory. 当我们尝试加载HashMap时,我们的System ClassLoader将其委托给Extension ClassLoader。 扩展类加载器将其委托给Bootstrap ClassLoader。 引导类加载器会找到HashMap类并将其加载到JVM内存中。 * The same process is followed for the DNSNameService class. But, the Bootstrap ClassLoader is not able to locate it since it’s in `$JAVA_HOME/lib/ext/dnsns.jar`. Hence, it gets loaded by Extensions Classloader. DNSNameService类遵循相同的过程。 但是,由于Bootstrap ClassLoader位于`$JAVA_HOME/lib/ext/dnsns.jar`因此无法找到它。 因此,它由扩展类加载器加载。 * The Blob class is included in the MySql [JDBC][] Connector jar (mysql-connector-java-5.0.7-bin.jar), which is present in the build path of the project. It’s also getting loaded by the System Classloader. Blob类包含在MySql [JDBC][]连接器jar(mysql-connector-java-5.0.7-bin.jar)中,该jar存在于项目的构建路径中。 系统类加载器也正在加载它。 * The classes loaded by a child class loader have visibility into classes loaded by its parent class loaders. So classes loaded by System Classloader have visibility into classes loaded by Extensions and Bootstrap Classloader. 子类加载器加载的类可以查看其父类加载器加载的类。 因此,由System Classloader加载的类可以查看由Extensions和Bootstrap Classloader加载的类。 * If there are sibling class loaders then they can’t access classes loaded by each other. 如果有同级类加载器,则它们将无法访问彼此加载的类。 ## 为什么要用Java编写自定义ClassLoader? **(****Why write a Custom ClassLoader in Java?****)** ## Java default ClassLoader can load classes from the local file system, which is good enough for most of the cases. But, if you are expecting a class at the runtime or from the FTP server or via third party web service at the time of loading the class, then you have to extend the existing class loader. For example, AppletViewers load the classes from a remote web server. Java默认的ClassLoader可以从本地文件系统加载类,这在大多数情况下已经足够了。 但是,如果在加载类时希望在运行时或从FTP服务器或通过第三方Web服务获取类,则必须扩展现有的类加载器。 例如,AppletViewers从远程Web服务器加载类。 ## Java ClassLoader方法 **(****Java ClassLoader Methods****)** ## * When JVM requests for a class, it invokes `loadClass()` function of the ClassLoader by passing the fully classified name of the Class. JVM请求一个类时,它将通过传递类的完全分类名称来调用ClassLoader的`loadClass()`函数。 * The loadClass() function calls the `findLoadedClass()` method to check that the class has been already loaded or not. It’s required to avoid loading the same class multiple times. loadClass()函数调用`findLoadedClass()`方法以检查是否已加载该类。 需要避免多次加载同一类。 * If the Class is not already loaded, then it will delegate the request to parent ClassLoader to load the class. 如果尚未加载该类,则它将把请求委托给父ClassLoader来加载该类。 * If the parent ClassLoader doesn’t find the class then it will invoke findClass() method to look for the classes in the file system. 如果父类ClassLoader找不到该类,则它将调用findClass()方法在文件系统中查找这些类。 ## Java自定义ClassLoader示例 **(****Java Custom ClassLoader Example****)** ## We will create our own ClassLoader by extending the ClassLoader class and overriding the loadClass(String name) method. 我们将通过扩展ClassLoader类并覆盖loadClass(String name)方法来创建自己的ClassLoader。 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. 如果类名将从`com.journaldev`开始,则我们将使用自定义类加载器加载它,否则我们将调用父ClassLoader `loadClass()`方法加载该类。 ### 1. CCLoader.java **(****1. CCLoader.java****)** ### This is our custom class loader with below methods. 这是带有以下方法的自定义类加载器。 1. `private byte[] loadClassFileData(String name)`: This method will read the class file from file system to byte array. `private byte[] loadClassFileData(String name)` :此方法将从文件系统读取类文件到字节数组。 2. `private Class<?> getClass(String name)`: This method will call the loadClassFileData() function and by invoking the parent defineClass() method, it will generate the Class and return it. `private Class<?> getClass(String name)` :此方法将调用loadClassFileData()函数,并通过调用父defineClass()方法,将生成Class并返回它。 3. `public Class<?> loadClass(String name)`: This method is responsible for loading the class. If the class name starts with com.journaldev (Our sample classes) then it will load it using getClass() method or else it will invoke the parent loadClass() function to load it. `public Class<?> loadClass(String name)` :此方法负责加载类。 如果类名以com.journaldev(我们的示例类)开头,则它将使用getClass()方法加载它,否则它将调用父loadClass()函数来加载它。 4. `public CCLoader(ClassLoader parent)`: This is the constructor, which is responsible for setting the parent ClassLoader. `public CCLoader(ClassLoader parent)` :这是构造函数,负责设置父ClassLoader。 import java.io.DataInputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; /** * Our Custom ClassLoader to load the classes. Any class in the com.journaldev * package will be loaded using this ClassLoader. For other classes, it will delegate the request to its Parent ClassLoader. * */ public class CCLoader extends ClassLoader { /** * This constructor is used to set the parent ClassLoader */ public CCLoader(ClassLoader parent) { super(parent); } /** * Loads the class from the file system. The class file should be located in * the file system. The name should be relative to get the file location * * @param name * Fully Classified name of the class, for example, com.journaldev.Foo */ private Class getClass(String name) throws ClassNotFoundException { String file = name.replace('.', File.separatorChar) + ".class"; byte[] b = null; try { // This loads the byte code data from the file b = loadClassFileData(file); // defineClass is inherited from the ClassLoader class // that converts byte array into a Class. defineClass is Final // so we cannot override it Class c = defineClass(name, b, 0, b.length); resolveClass(c); return c; } catch (IOException e) { e.printStackTrace(); return null; } } /** * Every request for a class passes through this method. If the class is in * com.journaldev package, we will use this classloader or else delegate the * request to parent classloader. * * * @param name * Full class name */ @Override public Class loadClass(String name) throws ClassNotFoundException { System.out.println("Loading Class '" + name + "'"); if (name.startsWith("com.journaldev")) { System.out.println("Loading Class using CCLoader"); return getClass(name); } return super.loadClass(name); } /** * Reads the file (.class) into a byte array. The file should be * accessible as a resource and make sure that it's not in Classpath to avoid * any confusion. * * @param name * Filename * @return Byte array read from the file * @throws IOException * if an exception comes in reading the file */ private byte[] loadClassFileData(String name) throws IOException { InputStream stream = getClass().getClassLoader().getResourceAsStream( name); int size = stream.available(); byte buff[] = new byte[size]; DataInputStream in = new DataInputStream(stream); in.readFully(buff); in.close(); return buff; } } ### 2. CCRun.java **(****2. CCRun.java****)** ### This is our test class with the [main function][]. We are creating an instance of our ClassLoader and loading sample classes using its loadClass() method. 这是带有[主要功能的][main function]测试类。 我们正在创建ClassLoader的实例,并使用其loadClass()方法加载示例类。 After loading the class, we are using [Java Reflection API][] to invoke its methods. 加载该类之后,我们将使用[Java Reflection API][]来调用其方法。 import java.lang.reflect.Method; public class CCRun { public static void main(String args[]) throws Exception { String progClass = args[0]; String progArgs[] = new String[args.length - 1]; System.arraycopy(args, 1, progArgs, 0, progArgs.length); CCLoader ccl = new CCLoader(CCRun.class.getClassLoader()); Class clas = ccl.loadClass(progClass); Class mainArgType[] = { (new String[0]).getClass() }; Method main = clas.getMethod("main", mainArgType); Object argsArray[] = { progArgs }; main.invoke(null, argsArray); // Below method is used to check that the Foo is getting loaded // by our custom class loader i.e CCLoader Method printCL = clas.getMethod("printCL", null); printCL.invoke(null, new Object[0]); } } ### 3. Foo.java和Bar.java **(****3. Foo.java and Bar.java****)** ### These are our test classes that are getting loaded by our custom classloader. They have a `printCL()` method, which is getting invoked to print the ClassLoader information. 这些是由我们的自定义类加载器加载的测试类。 它们具有一个`printCL()`方法,该方法将被调用以打印ClassLoader信息。 Foo class will be loaded by our custom class loader. Foo uses Bar class, so Bar class will also be loaded by our custom class loader. Foo类将由我们的自定义类加载器加载。 Foo使用Bar类,因此Bar类也将由我们的自定义类加载器加载。 package com.journaldev.cl; public class Foo { static public void main(String args[]) throws Exception { System.out.println("Foo Constructor >>> " + args[0] + " " + args[1]); Bar bar = new Bar(args[0], args[1]); bar.printCL(); } public static void printCL() { System.out.println("Foo ClassLoader: "+Foo.class.getClassLoader()); } } package com.journaldev.cl; public class Bar { public Bar(String a, String b) { System.out.println("Bar Constructor >>> " + a + " " + b); } public void printCL() { System.out.println("Bar ClassLoader: "+Bar.class.getClassLoader()); } } ### 4. Java自定义ClassLoader执行步骤 **(****4. Java Custom ClassLoader Execution Steps****)** ### First of all, we will compile all the classes through the command line. After that, we will run the CCRun class by passing three arguments. The first argument is the fully classified name for Foo class that will get loaded by our class loader. Other two arguments are passed along to the Foo class main function and Bar constructor. The execution steps and the output will be like below. 首先,我们将通过命令行编译所有类。 之后,我们将通过传递三个参数来运行CCRun类。 第一个参数是Foo类的完全分类名称,它将由我们的类加载器加载。 其他两个参数将传递给Foo类的主函数和Bar构造函数。 执行步骤和输出将如下所示。 $ javac -cp . com/journaldev/cl/Foo.java $ javac -cp . com/journaldev/cl/Bar.java $ javac CCLoader.java $ javac CCRun.java CCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter; cast to java.lang.Class<?> for a varargs call cast to java.lang.Class<?>[] for a non-varargs call and to suppress this warning Method printCL = clas.getMethod("printCL", null); ^ 1 warning $ java CCRun com.journaldev.cl.Foo 1212 1313 Loading Class 'com.journaldev.cl.Foo' Loading Class using CCLoader Loading Class 'java.lang.Object' Loading Class 'java.lang.String' Loading Class 'java.lang.Exception' Loading Class 'java.lang.System' Loading Class 'java.lang.StringBuilder' Loading Class 'java.io.PrintStream' Foo Constructor >>> 1212 1313 Loading Class 'com.journaldev.cl.Bar' Loading Class using CCLoader Bar Constructor >>> 1212 1313 Loading Class 'java.lang.Class' Bar ClassLoader: CCLoader@71f6f0bf Foo ClassLoader: CCLoader@71f6f0bf $ If you look at the output, it’s trying to load `com.journaldev.cl.Foo` class. Since it’s extending java.lang.Object class, it’s trying to load Object class first. 如果查看输出,它将尝试加载`com.journaldev.cl.Foo`类。 由于它扩展了java.lang.Object类,因此它尝试首先加载Object类。 So the request is coming to CCLoader loadClass method, which is delegating it to the parent class. So the parent class loaders are loading the Object, String, and other java classes. 因此,该请求即将到达CCLoader loadClass方法,该方法将其委派给父类。 因此,父类加载器正在加载Object,String和其他Java类。 Our ClassLoader is only loading Foo and Bar class from the file system. It’s clear from the output of the printCL() function. 我们的ClassLoader仅从文件系统加载Foo和Bar类。 从printCL()函数的输出中可以很清楚地看到。 We can change the loadClassFileData() functionality to read the byte array from FTP Server or by invoking any third party service to get the class byte array on the fly. 我们可以更改loadClassFileData()功能以从FTP服务器读取字节数组,或者通过调用任何第三方服务来即时获取类字节数组。 I hope that the article will be useful in understanding Java ClassLoader working and how we can extend it to do a lot more than just taking it from the file system. 我希望这篇文章对理解Java ClassLoader的工作以及如何扩展它的作用不仅仅适用于从文件系统中获取知识,这将是有益的。 ## 将自定义ClassLoader设置为默认ClassLoader **(****Making Custom ClassLoader as Default ClassLoader****)** ## We can make our custom class loader as the default one when JVM starts by using Java Options. 通过使用Java选项,可以在JVM启动时将自定义类装入器作为默认装入器。 For example, I will run the ClassLoaderTest program once again after providing the java classloader option. 例如,在提供java classloader选项之后,我将再次运行ClassLoaderTest程序。 $ javac -cp .:../lib/mysql-connector-java-5.0.7-bin.jar com/journaldev/classloader/ClassLoaderTest.java $ java -cp .:../lib/mysql-connector-java-5.0.7-bin.jar -Djava.system.class.loader=CCLoader com.journaldev.classloader.ClassLoaderTest Loading Class 'com.journaldev.classloader.ClassLoaderTest' Loading Class using CCLoader Loading Class 'java.lang.Object' Loading Class 'java.lang.String' Loading Class 'java.lang.System' Loading Class 'java.lang.StringBuilder' Loading Class 'java.util.HashMap' Loading Class 'java.lang.Class' Loading Class 'java.io.PrintStream' class loader for HashMap: null Loading Class 'sun.net.spi.nameservice.dns.DNSNameService' class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457 class loader for this class: CCLoader@38503429 Loading Class 'com.mysql.jdbc.Blob' sun.misc.Launcher$AppClassLoader@2f94ca6c $ The CCLoader is loading the ClassLoaderTest class because its in `com.journaldev` package. CCLoader正在加载ClassLoaderTest类,因为它位于`com.journaldev`程序包中。 [GitHub Repository][]. [GitHub Repository][]下载ClassLoader示例代码。 > 翻译自: [https://www.journaldev.com/349/java-classloader][https_www.journaldev.com_349_java-classloader] [Java Virtual Machine]: https://www.journaldev.com/546/difference-jdk-vs-jre-vs-jvm [java.util.HashMap]: https://www.journaldev.com/11560/java-hashmap [JDBC]: https://www.journaldev.com/2681/jdbc-tutorial [main function]: https://www.journaldev.com/12552/public-static-void-main-string-args-java-main-method [Java Reflection API]: https://www.journaldev.com/1789/java-reflection-example-tutorial [GitHub Repository]: https://github.com/journaldev/journaldev/tree/master/CoreJavaProjects/JavaPrograms/src [https_www.journaldev.com_349_java-classloader]: https://www.journaldev.com/349/java-classloader
还没有评论,来说两句吧...