本文共 17024 字,大约阅读时间需要 56 分钟。
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创建自定义类加载器。
We know that Java Program runs on (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程序在 (JVM)上运行。 当我们编译Java类时,JVM将创建字节码,该字节码与平台和机器无关。 字节码存储在.class文件中 。 当我们尝试使用一个类时,ClassLoader将其加载到内存中。
There are three types of built-in ClassLoader in Java.
Java内置了三种类型的内置ClassLoader。
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: nullclass loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@7c354093class loader for this class: sun.misc.Launcher$AppClassLoader@64cbbe37sun.misc.Launcher$AppClassLoader@64cbbe37
Let’s understand the working of class loaders from the above program output.
让我们从上面的程序输出中了解类加载器的工作方式。
$JAVA_HOME/lib/ext/dnsns.jar
. Hence, it gets loaded by Extensions Classloader. DNSNameService类遵循相同的过程。 但是,由于Bootstrap ClassLoader位于$JAVA_HOME/lib/ext/dnsns.jar
因此无法找到它。 因此,它由扩展类加载器加载。 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服务器加载类。
loadClass()
function of the ClassLoader by passing the fully classified name of the Class. JVM请求一个类时,它将通过传递类的完全分类名称来调用ClassLoader的loadClass()
函数。 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()
方法以检查是否已加载该类。 需要避免多次加载同一类。 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()
方法加载该类。
This is our custom class loader with below methods.
这是带有以下方法的自定义类加载器。
private byte[] loadClassFileData(String name)
: This method will read the class file from file system to byte array. private byte[] loadClassFileData(String name)
:此方法将从文件系统读取类文件到字节数组。 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并返回它。 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()函数来加载它。 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; }}
This is our test class with the . We are creating an instance of our ClassLoader and loading sample classes using its loadClass() method.
这是带有测试类。 我们正在创建ClassLoader的实例,并使用其loadClass()方法加载示例类。
After loading the class, we are using to invoke its methods.
加载该类之后,我们将使用来调用其方法。
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]); } }
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()); }}
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.javaCCRun.java:18: warning: non-varargs call of varargs method with inexact argument type for last parameter;cast to java.lang.Class for a varargs callcast to java.lang.Class [] for a non-varargs call and to suppress this warningMethod printCL = clas.getMethod("printCL", null);^1 warning$ java CCRun com.journaldev.cl.Foo 1212 1313Loading Class 'com.journaldev.cl.Foo'Loading Class using CCLoaderLoading 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 1313Loading Class 'com.journaldev.cl.Bar'Loading Class using CCLoaderBar Constructor >>> 1212 1313Loading Class 'java.lang.Class'Bar ClassLoader: CCLoader@71f6f0bfFoo 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的工作以及如何扩展它的作用不仅仅适用于从文件系统中获取知识,这将是有益的。
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.ClassLoaderTestLoading Class 'com.journaldev.classloader.ClassLoaderTest'Loading Class using CCLoaderLoading 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: nullLoading Class 'sun.net.spi.nameservice.dns.DNSNameService'class loader for DNSNameService: sun.misc.Launcher$ExtClassLoader@24480457class loader for this class: CCLoader@38503429Loading 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
程序包中。
翻译自:
转载地址:http://haozd.baihongyu.com/