What is ClassLoader? Among all programming languages, Java is unique in that it runs on the Java Virtual Machine. This means that the compiled program will run on the target machine in a unique, platform-independent form other than the target machine's format. This format is very different from traditional executable programs in many aspects.
Java ClassLoader is a crucial but often overlooked component in the Java operating system. It is responsible for finding and loading class files at runtime. Creating a custom ClassLoader can completely redefine how class files are loaded into the system.
This tutorial provides an overall overview of Java's ClassLoader and gives an example of a custom ClassLoader. This ClassLoader will automatically compile before loading the code. You'll learn what a ClassLoader does and how to create a custom ClassLoader.
This tutorial requires readers to have a basic understanding of Java programming, including creating, compiling, and executing simple command-line Java programs.
After reading this tutorial, you will know how to:
Extend the functionality of the JVM
Create a custom ClassLoader
Learn how to integrate ClassLoader into Java applications
Modify ClassLoader to conform to the Java2 version
Among all programming languages, Java is unique in that it runs on the Java Virtual Machine. This means that the compiled program will run on the target machine in a unique, platform-independent form other than the target machine's format. This format is very different from traditional executable programs in many aspects.
The biggest difference between a Java program and a C or C program is that it is not a single executable file, but consists of many separate class files, each class file corresponding to a Java class.
Not only that, these class files are not loaded into memory at once, but on demand. ClassLoader is the part of JVM that loads classes into memory.
In addition, Java ClassLoader is written in Java. This means that you can easily create your own ClassLoader without knowing more details about the JVM.
If the JVM already has a ClassLoader, why do you need to write another one? Good question, the default ClassLoader only knows how to load class files from the local file system. In general scenarios, when you write code locally and compile it locally, it is completely sufficient.
However, one of the most novel features of the JAVA language is that classes can be obtained from the local hard drive or outside the Internet. For example, the browser uses a custom ClassLoader to obtain executable content from the website.
There are many other ways to obtain class files. In addition to loading class files locally or from the Internet, you can also use a class loader to:
Automatically verify digital signatures before executing untrusted code
Transparent decryption code using password provided by user
Create custom dynamic classes based on user’s specific needs
Any generation The content of Java bytecode can be integrated into your application.
If you have ever used an applet, you must have used a custom class loader.
When Sun released the Java language, one of the most exciting things was watching how the technology performed timely loading of code from a remote Web server. The exciting thing is that they send bytecode over an HTTP connection from a remote web server and run it locally.
The Java language's ability to support custom ClassLoader makes this idea possible. There is a custom ClassLoader in the applet. It does not load the class file from the local file system, but obtains it from the remote Web server, loads the original bytecode through Http, and then converts it into a class in the jvm.
Class loaders in browsers and Applets also have other functions: security management, preventing applets on different pages from affecting each other, etc.
Next we will create a custom class loader called CompilingClassLoader(CCL)
. CCL will help us compile Java code. It's basically like building a simple make program directly into the running system.
The basic purpose of ClassLoader is to provide services for class requests. The JVM needs a class, so it asks the ClassLoader to load the class by its name. ClassLoader attempts to return an object representing the class.
A custom ClassLoader can be created by overriding the methods corresponding to different stages of this process.
In the remainder of this article, you will learn about some key methods in ClassLoader. You'll learn what each method does and how it's called during class loading. You'll also learn what needs to be done when you customize a ClassLoader.
loadClass
Method##, ClassLoader.loadClass()
method is the entrance to ClassLoader. Its method tag is as follows:
Class loadClass(String name, boolean resolve)
name
The parameter represents the name of the class required by the JVM, such as Foo
or java.lang.Object
.
resolve
The parameter indicates whether the class needs to be resolved. Class parsing can be understood as completely preparing the class for execution. Parsing is not necessary. If the JVM only needs to determine the existence of the class or find out its parent class, there is no need to parse.
Before the java1.1 version, custom ClassLoader only needed to override the loadClass
method.
defineClass
method is the core of the entire ClassLoader. This method converts the original byte array into a Class
object. The raw byte array contains data obtained locally or remotely.
defineClass
is responsible for handling many complex, mysterious and implementation-dependent parts of the JVM. It parses the bytecode into runtime data structures, checks their validity, etc. Don't worry, you don't have to implement this yourself. In fact, you can't override it at all because the method is final.
findSystemClass method
findSysetmClass
method loads files from the local file system. It looks for a class file in the local file system and, if present, converts it from raw bytes to a class object using defineClass
. This is the JVM's default mechanism for loading classes when running a Java application.
For custom ClassLoader, we will only call the findSystemClass
method after trying other methods to load the class content. The reason is simple: a custom ClassLoader contains some steps for loading special classes, but not all classes are special classes. For example, even if the ClassLoader needs to get some classes from the remote website, there are still many classes that need to be loaded from the local Java library. These classes are not our focus, so we need the JVM to obtain them in the default way.
The entire process is as follows:
Request a custom ClassLoader to load a class
Check whether the remote server has the class
If there is, get and return
If not, we assume that the class is a local base class and call findSystemClass
Load from the file system.
In most custom ClassLoaders, you need to first use findSystemClass
to reduce access to the remote website, because most Java classes are located locally in the class library. However, as you'll see in the next section, we don't want the JVM to load classes from the local file system before automatically compiling the application code.
resolveClass
MethodAs mentioned above, class loading can be done partially (without parsing) or completely (with parsing). When we implement our own loadClass
method, we may need to call the resolveClass
method, depending on the value of the resolve
parameter in loadClass
.
findLoadedClass
methodfindLoadedClass
method acts as a caching calling mechanism: when the loadClass
method is called, he will call This method is used to check whether the class has been loaded, eliminating the need for repeated loading. This method should be called first.
In our exampleloadClass
perform the following steps (here we will not pay special attention to what magical method is used to obtain the class file. It can It is obtained from local, network or compressed files. In short, we obtain the bytecode of the original class file):
Call findLoadedClass
to check whether it has been loaded Pass the class
If not, use magic to get the raw bytecode
If get the bytecode, call defineClass
Convert it into a Class
object
If the bytecode is not obtained, call findSystemClass
to see if it can be obtained from The local file system obtains the class
If the resolve
value is true, resolveClass
is called to resolve the Class
object
If the class is still not found, throw ClassNotFoundException
Otherwise, return the class to the caller
CompilingClassLoader
The role of CCL
is to ensure that the code has been compiled and is the latest version.
The following is a description of this class:
When a class is needed, check whether the class is on disk, in the current directory or the corresponding subdirectory
If the class does not exist, but its source code exists, call the Java compiler to generate the class file
If the class file exists, check whether it is The version of the source code is old. If it is lower than the version of the source code, regenerate the class file
If the compilation fails, or other reasons prevent the class file from being generated from the source code, throw ClassNotFoundException
If there is still no class file, then it may be in some other libraries, call findSystemClass
to see if it is useful
If the class is still not found, throw ClassNotFoundException
Otherwise, return the class
在深入研究之前,我们应该回过头来看一下Java的编译机制。总的来说,当你请求一个类的时候,Java不只是编译各种类信息,它还编译了别的相关联的类。
CCL会按需一个接一个的编译相关的类。但是,当CCL编译完一个类之后试着去编译其它相关类的时候会发现,其它的类已经编译完成了。为什么呢?Java编译器遵循一个规则:如果一个类不存在,或者它相对于源码已经过时了,就需要编译它。从本质上讲,Java编译器先CCL一步完成了大部分的工作。
CCL在编译类的时候会打印其编译的应用程序。在大多数场景里面,你会看到它在程序的主类上调用编译器。
但是,有一种情况是不会在第一次调用时编译所有类的的。如果你通过类名Class.forNasme
加载一个类,Java编译器不知道该类需要哪些信息。在这种场景下,你会看到CCL会再次运行Java编译器。
CompilingClassLoader
为了使用CCL,我们需要用一种独特的方式启动程序。正常的启动程序如下:
% java Foo arg1 arg2
而我们启动方式如下:
% java CCLRun Foo arg1 arg2
CCLRun是一个特殊的桩程序,它会创建一个CompilingClassLoader并使用它来加载程序的main方法,确保整个程序的类会通过CompilingClassLoader加载。CCLRun使用Java反射API来调用main方法并传参
Java1.2以后ClassLoader有一些变动。原有版本的ClassLoader还是兼容的,而且在新版本下开发ClassLoader更容易了
新的版本下采用了delegate模型。ClassLoader可以将类的请求委托给父类。默认的实现会先调用父类的实现,在自己加载。但是这种模式是可以改变的。所有的ClassLoader的根节点是系统ClassLoader。它默认会从文件系统中加载类。
loadClass
默认实现一个自定义的loadClass
方法通常会尝试用各种方法来获得一个类的信息。如果你写了大量的ClassLoader,你会发现基本上是在重复写复杂而变化不大的代码。
java1.2的loadClass
的默认实现中允许你直接重写findClass
方法,loadClass
将会在合适的时候调用该方法。
这种方式的好处在于你无须重写loadClass
方法。
findClass
该方法会被loadClass
的默认实现调用。findClass
是为了包含ClassLoader所有特定的代码,而无需写大量重负的其他代码
getSystenClassLoader
无论你是否重写了findClass
或是loadClass
方法,getSystemClassLoader
允许你直接获得系统的ClassLoader(而不是隐式的用findSystemClass
获得)
getParent
该方法允许类加载器获取其父类加载器,从而将请求委托给它。当你自定义的加载器无法找到类时,可以使用该方法。父类加载器是指包含创建该类加载代码的加载器。
// $Id$ import java.io.*; /* A CompilingClassLoader compiles your Java source on-the-fly. It checks for nonexistent .class files, or .class files that are older than their corresponding source code. */ public class CompilingClassLoader extends ClassLoader { // Given a filename, read the entirety of that file from disk // and return it as a byte array. private byte[] getBytes( String filename ) throws IOException { // Find out the length of the file File file = new File( filename ); long len = file.length(); // Create an array that's just the right size for the file's // contents byte raw[] = new byte[(int)len]; // Open the file FileInputStream fin = new FileInputStream( file ); // Read all of it into the array; if we don't get all, // then it's an error. int r = fin.read( raw ); if (r != len) throw new IOException( "Can't read all, "+r+" != "+len ); // Don't forget to close the file! fin.close(); // And finally return the file contents as an array return raw; } // Spawn a process to compile the java source code file // specified in the 'javaFile' parameter. Return a true if // the compilation worked, false otherwise. private boolean compile( String javaFile ) throws IOException { // Let the user know what's going on System.out.println( "CCL: Compiling "+javaFile+"..." ); // Start up the compiler Process p = Runtime.getRuntime().exec( "javac "+javaFile ); // Wait for it to finish running try { p.waitFor(); } catch( InterruptedException ie ) { System.out.println( ie ); } // Check the return code, in case of a compilation error int ret = p.exitValue(); // Tell whether the compilation worked return ret==0; } // The heart of the ClassLoader -- automatically compile // source as necessary when looking for class files public Class loadClass( String name, boolean resolve ) throws ClassNotFoundException { // Our goal is to get a Class object Class clas = null; // First, see if we've already dealt with this one clas = findLoadedClass( name ); //System.out.println( "findLoadedClass: "+clas ); // Create a pathname from the class name // E.g. java.lang.Object => java/lang/Object String fileStub = name.replace( '.', '/' ); // Build objects pointing to the source code (.java) and object // code (.class) String javaFilename = fileStub+".java"; String classFilename = fileStub+".class"; File javaFile = new File( javaFilename ); File classFile = new File( classFilename ); //System.out.println( "j "+javaFile.lastModified()+" c "+ // classFile.lastModified() ); // First, see if we want to try compiling. We do if (a) there // is source code, and either (b0) there is no object code, // or (b1) there is object code, but it's older than the source if (javaFile.exists() && (!classFile.exists() || javaFile.lastModified() > classFile.lastModified())) { try { // Try to compile it. If this doesn't work, then // we must declare failure. (It's not good enough to use // and already-existing, but out-of-date, classfile) if (!compile( javaFilename ) || !classFile.exists()) { throw new ClassNotFoundException( "Compile failed: "+javaFilename ); } } catch( IOException ie ) { // Another place where we might come to if we fail // to compile throw new ClassNotFoundException( ie.toString() ); } } // Let's try to load up the raw bytes, assuming they were // properly compiled, or didn't need to be compiled try { // read the bytes byte raw[] = getBytes( classFilename ); // try to turn them into a class clas = defineClass( name, raw, 0, raw.length ); } catch( IOException ie ) { // This is not a failure! If we reach here, it might // mean that we are dealing with a class in a library, // such as java.lang.Object } //System.out.println( "defineClass: "+clas ); // Maybe the class is in a library -- try loading // the normal way if (clas==null) { clas = findSystemClass( name ); } //System.out.println( "findSystemClass: "+clas ); // Resolve the class, if any, but only if the "resolve" // flag is set to true if (resolve && clas != null) resolveClass( clas ); // If we still don't have a class, it's an error if (clas == null) throw new ClassNotFoundException( name ); // Otherwise, return the class return clas; } }
import java.lang.reflect.*; /* CCLRun executes a Java program by loading it through a CompilingClassLoader. */ public class CCLRun { static public void main( String args[] ) throws Exception { // The first argument is the Java program (class) the user // wants to run String progClass = args[0]; // And the arguments to that program are just // arguments 1..n, so separate those out into // their own array String progArgs[] = new String[args.length-1]; System.arraycopy( args, 1, progArgs, 0, progArgs.length ); // Create a CompilingClassLoader CompilingClassLoader ccl = new CompilingClassLoader(); // Load the main class through our CCL Class clas = ccl.loadClass( progClass ); // Use reflection to call its main() method, and to // pass the arguments in. // Get a class representing the type of the main method's argument Class mainArgType[] = { (new String[0]).getClass() }; // Find the standard main method in the class Method main = clas.getMethod( "main", mainArgType ); // Create a list containing the arguments -- in this case, // an array of strings Object argsArray[] = { progArgs }; // Call the method main.invoke( null, argsArray ); } }
public class Foo { static public void main( String args[] ) throws Exception { System.out.println( "foo! "+args[0]+" "+args[1] ); new Bar( args[0], args[1] ); } }
import baz.*; public class Bar { public Bar( String a, String b ) { System.out.println( "bar! "+a+" "+b ); new Baz( a, b ); try { Class booClass = Class.forName( "Boo" ); Object boo = booClass.newInstance(); } catch( Exception e ) { e.printStackTrace(); } } }
package baz; public class Baz { public Baz( String a, String b ) { System.out.println( "baz! "+a+" "+b ); } }
public class Boo { public Boo() { System.out.println( "Boo!" ); } }
相关文章:
基于Java类的加载方式之classloader类加载器详解
相关视频:
The above is the detailed content of Why write ClassLoader? Deep understanding of classloader in java. For more information, please follow other related articles on the PHP Chinese website!