ClassLoader, which translates to ClassLoader, is rarely used by Java developers, but is common to some framework developers. Understanding the loading mechanism of ClassLoader also helps us write more efficient code. The ClassLoader loads the class file into the JVM, and the program runs correctly. However, instead of loading all class files at once when the JVM starts, it loads them dynamically as needed. If you load so many jars and classes at once, memory will not crash. The purpose of this article is also to learn about the ClassLoader loading mechanism.

Class file awareness

We all know that programs in Java are run in virtual machines, we usually use a text editor or IDE to write programs are. Java format files, which is the most basic source code, but this kind of file is not directly run. As we write a simple program helloWorld.java

public class HelloWorld{

	public static void main(String[] args){
		System.out.println("Hello world!");
	}
}
Copy the code

As shown in figure:

Next, we need to compile the Java files on the command line

javac HelloWorld.java
Copy the code

You can see that a.class file is generated under the directory

From the command line we execute the following command:

java HelloWorld
Copy the code

Class files are bytecode files, and the JAVA virtual machine does not directly recognize the.java source files we normally write. So you need to convert the javac command into a.class file. In addition, if programs written in C or PYTHON are correctly converted to a. Class file, the Java virtual machine can recognize and run. You can refer to this article for more information.

With the.class file in mind, let’s think about how the Java programs we write in Eclipse work, that is, how the classes we write are loaded into the JVM.

Do you remember Java environment variables?

When I first learned Java, I was most afraid of configuring environment variables after downloading JDK. The key was that I did not understand at that time, so I followed the introduction of books or the network with fear. And then the next time I do it, I forget it and I always forget it. At that time, I was very angry, thinking that this thing is not humanized, why do you have to configure environment variables? Too little care is given to newbies and newbies, many of whom suffer too much frustration because they get stuck in the configuration of environment variables.

Since I am programming in Windows, I will only talk about the environment variables on the Windows platform. There are three main variables: JAVA_HOME, PATH, and CLASSPATH.

JAVA_HOME

This is where the JDK is installed. The default installation is on disk C

C: \ Program Files \ Java \ jdk1.8.0 _91Copy the code

PATH

By including the program PATH in the PATH, you can type its name in the command line window instead of typing its full PATH, as I did with the javac and Java commands in the code above. The general

PATH=%JAVA_HOME%\bin; %JAVA_HOME%\jre\bin; %PATH%;Copy the code

That is, add the bin directory in the JDK directory and the bin directory of the JRE directory to the original PATH.

CLASSPATH

CLASSPATH=.; %JAVA_HOME%\lib; %JAVA_HOME%\lib\tools.jarCopy the code

This is the path to the jar package. Note the first one. ,. Indicates the current directory.

Setting and viewing environment variables

Setting can right click my computer, and then click properties, then click advanced, and then click environment variables, specific do not understand their own documents.

You can open a command line window

echo %JAVA_HOME%

echo %PATH%

echo %CLASSPATH%
Copy the code

Okay, so with that out of the way, and with environment variables in mind, especially CLASSPATH, let’s move on to today’s topic Classloader.

JAVA class loading process

The Java language system comes with three class loaders:

  • Bootstrap ClassLoaderRt. jar, resources. Jar, charsets. Jar, and class in %JRE_HOME%\lib. Also note that you can change the loading directory of the Bootstrap ClassLoader by specifying -xBootCLASSPath and path when starting the JVM. Such asjava -Xbootclasspath/a:pathThe specified file is appended to the default bootstrap path. We can open my computer and look in the directory above to see if these JAR packages exist in that directory.
  • Extention ClassLoaderAn extended class loader that loads jar packages and class files in the %JRE_HOME%\lib\ext directory. You can also load-D java.ext.dirsOption to specify a directory.
  • Appclass Loader also called SystemAppClass loads all classes of the current application’s classpath.

We briefly introduced three ClassLoaders above. Indicates the path they are loaded from. The -xbootCLASspath and -d java.ext.dirs virtual machine options are also mentioned.

Load order?

We see the system’s three classloaders, but we may not know which comes first. I can tell you the answer first

  1. Bootstrap CLassloder
  2. Extention ClassLoader
  3. AppClassLoader

For a better understanding, look at the source code. Look at Sun.misc.Launcher, which is a portal application for the Java virtual machine.

public class Launcher { private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { // Create the extension class loader ClassLoader extcl; try { extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try { loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } thread.currentThread ().setContextClassLoader(loader); } /* * Returns the class loader used to launch the main application. */ public ClassLoader getClassLoader() { return loader; } /* * The class loader used for loading installed extensions. */ static class ExtClassLoader extends URLClassLoader {} /** * The class loader used for loading from java.class.path. * runs in a restricted security context. */ static class AppClassLoader extends URLClassLoader {}Copy the code

Source code has been simplified, we can get relevant information.

  1. Launcher initializes ExtClassLoader and AppClassLoader.
  2. The BootstrapClassLoader is not seen in the Launcher, but it passesSystem.getProperty("sun.boot.class.path")I get a stringbootClassPathThis should be the jar package path loaded by BootstrapClassLoader.

We can test what sun.boot.class.path is in code.

System.out.println(System.getProperty("sun.boot.class.path"));
Copy the code

The result is:

C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ resources jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ rt jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ sunrsasign jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ jsse jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ jce jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ charsets jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ JFR jar; C: \ Program Files \ Java \ jre1.8.0 _91 \ classesCopy the code

As you can see, these are all JAR packages or class files in the JRE directory.

ExtClassLoader source

If you are curious enough, you should be interested in its source code

/* * The class loader used for loading installed extensions. */ static class ExtClassLoader extends URLClassLoader { static { ClassLoader.registerAsParallelCapable(); } /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); try { // Prior implementations of this doPrivileged() block supplied // aa synthesized ACC via a call to the private method // ExtClassLoader.getContext(). return AccessController.doPrivileged( new PrivilegedExceptionAction<ExtClassLoader>() { public ExtClassLoader run() throws IOException { int len = dirs.length; for (int i = 0; i < len; i++) { MetaIndex.registerDirectory(dirs[i]); } return new ExtClassLoader(dirs); }}); } catch (java.security.PrivilegedActionException e) { throw (IOException) e.getException(); } } private static File[] getExtDirs() { String s = System.getProperty("java.ext.dirs"); File[] dirs; if (s ! = null) { StringTokenizer st = new StringTokenizer(s, File.pathSeparator); int count = st.countTokens(); dirs = new File[count]; for (int i = 0; i < count; i++) { dirs[i] = new File(st.nextToken()); } } else { dirs = new File[0]; } return dirs; }... }Copy the code

As we mentioned earlier, you can specify the -d java.ext.dirs parameter to add and change the ExtClassLoader loading path. Here we can write test code.

System.out.println(System.getProperty("java.ext.dirs"));
Copy the code

The results are as follows:

C: \ Program Files \ Java \ jre1.8.0 _91 \ lib \ ext. C:\Windows\Sun\Java\lib\extCopy the code

AppClassLoader source

/** * The class loader used for loading from java.class.path. * runs in a restricted security context. */ static class AppClassLoader extends URLClassLoader { public static ClassLoader getAppClassLoader(final ClassLoader extcl) throws IOException { final String s = System.getProperty("java.class.path"); final File[] path = (s == null) ? new File[0] : getClassPath(s); return AccessController.doPrivileged( new PrivilegedAction<AppClassLoader>() { public AppClassLoader run() { URL[] urls = (s == null) ? new URL[0] : pathToURLs(path); return new AppClassLoader(urls, extcl); }}); }... }Copy the code

You can see that the AppClassLoader loads the path under java.class.path. We also print its value.

System.out.println(System.getProperty("java.class.path"));
Copy the code

Results:

D:\workspace\ClassLoaderDemo\bin
Copy the code

This path is actually the current Java project directory bin, which holds the class files generated by compilation.

All right, BootstrapClassLoader, ExtClassLoader, and AppClassLoader actually check the corresponding environment attributes sun.boot.class.path, java.ext.dirs, and java.class.path To load the resource file.

Next we’ll explore their loading order, starting with a Java project in Eclipse.

Then create a test.java file.

public class Test{}
Copy the code

Then, write a classloadertest.java file.

public class ClassLoaderTest { public static void main(String[] args) { // TODO Auto-generated method stub ClassLoader cl = Test.class.getClassLoader(); System.out.println("ClassLoader is:"+cl.toString()); }}Copy the code

We get the classloader for the test. class file and print it out. The result is:

ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93  
Copy the code

The test. class file is loaded by the AppClassLoader.

We wrote the Test class ourselves, so who does the loading of int.class or string. class? We can try it in code

public class ClassLoaderTest { public static void main(String[] args) { // TODO Auto-generated method stub ClassLoader cl = Test.class.getClassLoader(); System.out.println("ClassLoader is:"+cl.toString()); cl = int.class.getClassLoader(); System.out.println("ClassLoader is:"+cl.toString()); }}Copy the code

Run, but error

ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" java.lang.NullPointerException
	at ClassLoaderTest.main(ClassLoaderTest.java:15)
Copy the code

A null pointer to an underlying class such as int.class is not loaded by a class loader.

Of course not! Int. class is loaded by the Bootstrap ClassLoader. To understand this, we first need to know a premise.

Each class loader has a parent loader

Each class loader has a parent loader. For example, AppClassLoader loads the test. class. It’s very simple, with the getParent method. For example, the code could be written like this:

ClassLoader cl = Test.class.getClassLoader();
		
System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
1234
Copy the code

The running results are as follows:

ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
Copy the code

The parent loader of AppClassLoader is ExtClassLoader. So who is the parent of the ExtClassLoader?

System.out.println("ClassLoader is:"+cl.toString());
System.out.println("ClassLoader\'s parent is:"+cl.getParent().toString());
System.out.println("ClassLoader\'s grand father is:"+cl.getParent().getParent().toString());
Copy the code

Run if:

ClassLoader is:sun.misc.Launcher$AppClassLoader@73d16e93
Exception in thread "main" ClassLoader's parent is:sun.misc.Launcher$ExtClassLoader@15db9742
java.lang.NullPointerException
	at ClassLoaderTest.main(ClassLoaderTest.java:13)
Copy the code

Another null pointer exception indicates that ExtClassLoader also has no parent. So why does a title have a parent loader for every loader? Isn’t that a contradiction? To explain this, we need to look at the following basic premise.

A parent loader is not a parent class

We pasted the code for ExtClassLoader and AppClassLoader earlier.

static class ExtClassLoader extends URLClassLoader {}
static class AppClassLoader extends URLClassLoader {}
Copy the code

You can see that ExtClassLoader and AppClassLoader inherit from URLClassLoader, but why does calling AppClassLoader’s getParent() code get an ExtClassLoader instance? Let’s start with URLClassLoader. What is this class? Start with a class inheritance diagram

The getParent() method is not found in the URLClassLoader source code. This method is in classLoader.java.

public abstract class ClassLoader { // The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent; // The class loader for the system // @GuardedBy("ClassLoader.class") private static ClassLoader scl; private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; . } protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public final ClassLoader getParent() { if (parent == null) return null; return parent; } public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } return scl; } private static synchronized void initSystemClassLoader() { if (! sclSet) { if (scl ! = null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l ! = null) { Throwable oops = null; SCL = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops ! = null) { if (oops instanceof Error) { throw (Error) oops; } else { // wrap the exception throw new Error(oops); } } } sclSet = true; }}}Copy the code

We can see that getParent() actually returns a ClassLoader object parent. The parent assignment is in the ClassLoader constructor.

  1. When creating a ClassLoader from an external class, specify a ClassLoader as parent directly.
  2. bygetSystemClassLoader()Method generation, which is passed in Sun.misc.lauchergetClassLoader()Get, which is AppClassLoader. To put it bluntly, if a ClassLoader is created without specifying parent, its parent defaults to AppClassLoader.

We’re looking at the origin of parent extClassLoaders and AppClassLoaders, which happen to be related to the Launcher class, part of which we pasted above.

public class Launcher { private static URLStreamHandlerFactory factory = new Factory(); private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { // Create the extension class loader ClassLoader extcl; try { extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } // Now create the class loader to use to launch the application try {// Pass the ExtClassLoader object to loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } public ClassLoader getClassLoader() { return loader; } static class ExtClassLoader extends URLClassLoader { /** * create an ExtClassLoader. The ExtClassLoader is created * within a context that limits which files it can read */ public static ExtClassLoader getExtClassLoader() throws IOException { final File[] dirs = getExtDirs(); try { // Prior implementations of this doPrivileged() block supplied // aa synthesized ACC via a call to the private method // ExtClassLoader.getContext(). return AccessController.doPrivileged( new PrivilegedExceptionAction < ExtClassLoader > () {public ExtClassLoader run () throws IOException {/ / ExtClassLoader created here return new ExtClassLoader(dirs); }}); } catch (java.security.PrivilegedActionException e) { throw (IOException) e.getException(); } } /* * Creates a new ExtClassLoader for the specified directories. */ public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); }}}Copy the code

We need to be aware of this

ClassLoader extcl;
        
extcl = ExtClassLoader.getExtClassLoader();

loader = AppClassLoader.getAppClassLoader(extcl);
Copy the code

The parent of AppClassLoader is an ExtClassLoader instance.

The ExtClassLoader does not find the parent assignment directly. It calls the constructor of its parent class, URLClassLoder, and passes in three arguments.

public ExtClassLoader(File[] dirs) throws IOException {
            super(getExtURLs(dirs), null, factory);   
}
Copy the code

Corresponding code

public  URLClassLoader(URL[] urls, ClassLoader parent,
                          URLStreamHandlerFactory factory) {
     super(parent);
}
Copy the code

The parent of ExtClassLoader is null.

The parent of an AppClassLoader is an ExtClassLoader, and the parent of an ExtClassLoader is null. This fits with the test code we wrote earlier.

BootstrapClassLoader, BootstrapClassLoader, BootstrapClassLoader, BootstrapClassLoader, BootstrapClassLoader

Also, the parent of ExtClassLoader is null, but Bootstrap CLassLoader can act as its parent. Why?

Let’s keep going.

Bootstrap ClassLoader is written in C++.

Bootstrap ClassLoader is written in C/C++. It is part of the virtual machine itself, so it is not a JAVA class. In other words, it cannot be referenced in JAVA code. When the JVM starts up, the Bootstrap class loader loads the class files in core JAR packages such as rt.jar. The previous int.class and String.class files are loaded by the Bootstrap class loader. Then, as we analyzed earlier, the JVM initializes sun.misc.Launcher and creates Extension ClassLoader and AppClassLoader instances. And set ExtClassLoader as the parent loader of AppClassLoader. Bootstrap does not have a parent loader, but it can act as the parent of a ClassLoader. Such as ExtClassLoader. This also explains why the getParent method of the ExtClassLoader was Null. We’ll soon know why.

Parents entrust

Parent delegate. We’re finally here. When a class loader looks for a class or resource, it does so in “delegate mode”. It determines whether the class has been successfully loaded. If not, it does not look for the class itself. If the Bootstrap classloader finds the object, the Bootstrap classloader returns the object directly. If the Bootstrap classloader does not find the object, the Bootstrap classloader returns the object level by level, and finally reaches itself to find the object. This mechanism is called parental delegation. The whole process can be shown as follows:

You can see the two arrows, the blue one representing the direction of the class loader delegate, and if the current class loader does not find that the class object has been loaded, it requests the parent loader (not necessarily the parent class) to do something about it, and so on. Until Bootstrap ClassLoader. If the Bootstrap ClassLoader has not loaded the class instance, it will find it in the specified path. If the Bootstrap ClassLoader has not found the class instance, it will return it. If the Bootstrap ClassLoader has not found the class instance, it will hand it to the subclass loader (ExtClassLoader). This is my example of the red arrow in the image above. Describe it in sequence:

  1. When an AppClassLoader looks for a resource, it first looks to see if the cache is present, and if the cache is retrieved from the cache, otherwise it delegates to the parent loader.
  2. Recursively, repeat the steps in Part 1.
  3. If the ExtClassLoader has not been loaded, the Bootstrap ClassLoader will first look for the cache. If it does not find the cache, the Bootstrap ClassLoader will find its own specified path, i.esun.mic.boot.classThe path below. If found, return. If not found, let the child loader look for it.
  4. If the Bootstrap ClassLoader fails to find the Bootstrap ClassLoader, the ExtClassLoader will find the Bootstrap ClassLoaderjava.ext.dirsPath to find, find success on the return, find unsuccessful, and then down to let the child loader to find.
  5. If the ExtClassLoader fails to find it, AppClassLoader will find it by itselfjava.class.pathPath. Find it and return. If it doesn’t find it, let the subclass find it, what happens if there’s no subclass? Throws various exceptions.

The sequence above details the loading flow of a parent delegate. We can see that the delegate is from the bottom up, and then the specific search process is from the top down.

I said that I was not satisfied with the sequence drawing above, so I will draw it again in the original way using block diagrams.

The loading process has been described in detail above, but there are several important methods to know about loadClass(), findLoadedClass(), findClass(), and defineClass().

Important method

loadClass()

The JDK documentation says that the class is loaded by the specified fully qualified class name, which is loaded through the loadClass(String, Boolean) method of the same name.

protected Class<? > loadClass(String name, boolean resolve) throws ClassNotFoundExceptionCopy the code

Above is the method prototype, and the general steps to implement this method are

  1. performfindLoadedClass(String)Check to see if the class is already loaded.
  2. Of the parent loaderloadClassMethods. If the parent loader is null, the JVM’s built-in loader is replaced by the Bootstrap ClassLoader. This also explains that the parent of ExtClassLoader is null, but the Bootstrap ClassLoader is still its parent loader.
  3. If the updelegate parent loader does not load successfully, passesfindClass(String)Lookup.

If class is found in the previous step, and resolve is true, loadClass() calls resolveClass(class) to generate the final class object. We can see this step in the source code.

protected Class<? > loadClass(String name, Boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock(name)) {// First check if Class<? > c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent ! LoadClass c = parent.loadClass(name, false); } else {Bootstrap Classloader c = findBootstrapClassOrNull(name); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. long t1 = System.nanoTime(); Findclass c = findClass(name); // this is the defining class loader; record the stats sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); sun.misc.PerfCounter.getFindClasses().increment(); ResolveClass (c); resolveClass(c); } return c; }}Copy the code

The code explains parental delegation.

Also, note that if you are writing a classLoader subclass, that is, a custom classLoader, it is recommended to override the findClass() method rather than overwrite the loadClass() method directly. In addition

if (parent ! LoadClass c = parent.loadClass(name, false); } else {Bootstrap Classloader c = findBootstrapClassOrNull(name); }Copy the code

The parent of ExtClassLoader is null, so when it delegates up, the Bootstrap ClassLoader is specified for it.

The custom this

Whether Bootstrap ClassLoader or ExtClassLoader, these class loaders only load JAR packages or resources in the specified directory. What if at some point we need to load something dynamically? Is it ok to load a class file from a folder on disk D, or download the main class content from the network and then load it?

To do so, we need to define a custom classloader.

Custom steps

  1. Write a class that inherits from the ClassLoader abstract class.
  2. Copy of itfindClass()Methods.
  3. infindClass()Method calldefineClass().

defineClass()

This method is very important when writing custom ClassLoaders. It converts the class binary content into a class object and throws various exceptions if it does not meet the requirements.

Note:

If a ClassLoader is created without parent specified, its parent defaults to AppClassLoader.

If you customize a ClassLoader, the default parent is AppClassLoader, because this ensures that it can access the class files that have been successfully loaded by the system’s built-in loader.

DiskClassLoader for custom ClassLoader example.

Suppose we need a custom classloader that loads jar packages and resources in D:\lib by default.

We write a Test class file, test.java

Test.java

package com.frank.test; public class Test { public void say(){ System.out.println("Say Hello"); }}Copy the code

Then compile the class file test. class and place it in D:\lib.

DiskClassLoader

We write the DiskClassLoader code.

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; public class DiskClassLoader extends ClassLoader { private String mLibPath; public DiskClassLoader(String path) { // TODO Auto-generated constructor stub mLibPath = path; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { // TODO Auto-generated method stub String fileName = getFileName(name); File file = new File(mLibPath,fileName); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; try { while ((len = is.read()) ! = -1) { bos.write(len); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } // Obtain the class file name to be loaded private String getFileName(String name) {// TODO auto-generated method Stub int index = name.lastIndexOf('.'); if(index == -1){ return name+".class"; }else{ return name.substring(index+1)+".class"; }}}Copy the code

We define the method to find the class in the findClass() method, and the data generates the class object through defineClass().

test

Now we’ll write the test code. We know that if we call the say method on a Test object, it will print the string “say Hello”. But now that we’ve placed test. class outside of the application project’s entire directory, we need to load it and then execute its methods. What are the specific effects? Will the DiskClassLoader we wrote get the job done? We’ll see.

import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; public class ClassLoaderTest { public static void main(String[] args) { // TODO Auto-generated method stub // Create a custom classloader object. DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib"); Try {// loadClass file class c = diskloader.loadclass (" com.frank.test.test "); if(c ! = null){ try { Object obj = c.newInstance(); Method method = c.getDeclaredMethod("say",null); // Invoke the Test say method by reflection (obj, null); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }}}Copy the code

We click the run button and the result is displayed.

As you can see, the Test say method executed correctly, which means that the DiskClassLoader was successfully written.

Looking back

With all this talk, custom ClassLoaders are late in coming. Many students may feel that the front is a little wordy, but I follow my own train of thought, I think it is necessary. Because I’m talking about one key word.

What are the keywords?

Keyword path

  • Environment variables from the beginning
  • To the three major JDK built-in classloaders
  • To a custom ClassLoader

The associated part is the path, which is the path of the class or resource to load. BootStrap ClassLoader, ExtClassLoader, and AppClassLoader load JAR packages in the specified path. If we want to break through this limitation and fulfill our special needs, we need to customize the ClassLoader and specify the loading path ourselves, which can be disk, memory, network or other.

So, do you think paths can be their keywords?

Of course, the above is just my personal view, may not be correct, but at this stage, so conducive to their own learning understanding.

What else can a custom ClassLoader do?

Once you’ve overcome the limitations of the JDK’s built-in loading paths, you can write custom ClassLoaders and leave the rest to the developer. You can customize the business as you wish and play around with the ClassLoader.

Play flower Class to decrypt the classloader

The common usage is to encrypt the Class file according to some encryption means, and then write a custom ClassLoader according to the rules to decrypt, so that we can load a specific Class in the program, and this Class can only be loaded by our custom loader, improve the security of the program.

Now, let’s write the code.

1. Define encryption and decryption protocols

There are many kinds of encryption and decryption protocols, depending on business needs. Here, for the sake of demonstration, I simply define encryption and decryption as an xOR operation. When an xOR operation is performed on a file, an encrypted file is generated, and another xOR operation is performed to decrypt the file.

2. Write encryption tool classes

import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; public class FileUtils { public static void test(String path){ File file = new File(path); try { FileInputStream fis = new FileInputStream(file); FileOutputStream fos = new FileOutputStream(path+"en"); int b = 0; int b1 = 0; try { while((b = fis.read()) ! Fos. write(b ^ 2); // fos.write(b ^ 2); } fos.close(); fis.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }}}Copy the code

Let’s write the test code

FileUtils.test("D:\\lib\\Test.class");
Copy the code

You can then see that test. class generates the test. classen file under path D:\\lib\\ test. class.

Write custom classloader, DeClassLoader

import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; public class DeClassLoader extends ClassLoader { private String mLibPath; public DeClassLoader(String path) { // TODO Auto-generated constructor stub mLibPath = path; } @Override protected Class<? > findClass(String name) throws ClassNotFoundException { // TODO Auto-generated method stub String fileName = getFileName(name); File file = new File(mLibPath,fileName); try { FileInputStream is = new FileInputStream(file); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len = 0; byte b = 0; try { while ((len = is.read()) ! = -1) {// Decrypt the data xor a number 2 b = (byte) (len ^ 2); bos.write(b); } } catch (IOException e) { e.printStackTrace(); } byte[] data = bos.toByteArray(); is.close(); bos.close(); return defineClass(name,data,0,data.length); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return super.findClass(name); } // Obtain the class file name to be loaded private String getFileName(String name) {// TODO auto-generated method Stub int index = name.lastIndexOf('.'); if(index == -1){ return name+".classen"; }else{ return name.substring(index+1)+".classen"; }}}Copy the code

test

We can code the main method in classLoadertest.java as follows:

DeClassLoader diskLoader = new DeClassLoader("D:\\lib"); Try {// loadClass file class c = diskloader.loadclass (" com.frank.test.test "); if(c ! = null){ try { Object obj = c.newInstance(); Method method = c.getDeclaredMethod("say",null); // Invoke the Test say method by reflection (obj, null); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }Copy the code

The result is as follows:

You can see it. It worked, too. Now, we have two custom this: DiskClassLoader and DeClassLoader, we can give it a try and see if DiskClassLoader can load Test. The classen file is the Test. The class the encrypted files.

We first remove the D:\\lib\\ test. class file, leaving only the test. classen file, and then Test the code.

DeClassLoader diskLoader1 = new DeClassLoader("D:\\lib"); Try {// load the class file class c = diskLoader1.loadClass(" com.frank.test.test "); if(c ! = null){ try { Object obj = c.newInstance(); Method method = c.getDeclaredMethod("say",null); // Invoke the Test say method by reflection (obj, null); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib"); Try {// loadClass file class c = diskloader.loadclass (" com.frank.test.test "); if(c ! = null){ try { Object obj = c.newInstance(); Method method = c.getDeclaredMethod("say",null); // Invoke the Test say method by reflection (obj, null); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); }}Copy the code

Running results:

We can see that. The DiskClassLoader cannot find the test. classen class, and it cannot load the test. classen file.

Context ClassLoader Thread Context ClassLoader

Bootstrap ClassLoader, ExtClassLoader, AppClassLoader, etc.

The first three are listed first because they are real classes and follow the “parent delegate” mechanism. The ContextClassLoader is really just a concept.

Look at the source code for Thread. Java

public class Thread implements Runnable { /* The context ClassLoader for this thread */ private ClassLoader contextClassLoader; public void setContextClassLoader(ClassLoader cl) { SecurityManager sm = System.getSecurityManager(); if (sm ! = null) { sm.checkPermission(new RuntimePermission("setContextClassLoader")); } contextClassLoader = cl; } public ClassLoader getContextClassLoader() { if (contextClassLoader == null) return null; SecurityManager sm = System.getSecurityManager(); if (sm ! = null) { ClassLoader.checkClassLoaderPermission(contextClassLoader, Reflection.getCallerClass()); } return contextClassLoader; }}Copy the code

ContextClassLoader is just a member variable, set by the setContextClassLoader() method and set by getContextClassLoader().

Each Thread has an associated ClassLoader, which is AppClassLoader by default. And the child thread defaults to using the parent thread’s ClassLoader unless the child thread specifically sets it.

We can also write code to further our understanding. Now there are two speakTest. class files, one source is

package com.frank.test; public class SpeakTest implements ISpeak { @Override public void speak() { // TODO Auto-generated method stub System.out.println("Test"); }}Copy the code

The generated speakTest. class file is placed in the D:\\lib\ test directory. In addition, ispeak.java code

 package com.frank.test;

public interface ISpeak {
	public void speak();

}
Copy the code

We then implemented a speakTest.java here

package com.frank.test; public class SpeakTest implements ISpeak { @Override public void speak() { // TODO Auto-generated method stub System.out.println("I\' frank"); }} 1234567891011Copy the code

The generated speakTest. class file is placed in the D:\\lib directory.

Then we need to write another ClassLoader, diskClassLoader1.java. This ClassLoader code is the same as diskClassLoader.java code. We need to load speakTest. class file in D:\\lib\ test in DiskClassLoader1.

Test code:

DiskClassLoader1 diskLoader1 = new DiskClassLoader1("D:\\lib\\test"); Class cls1 = null; Cls1 = diskLoader1.loadClass(" com.frank.test.speakTest "); System.out.println(cls1.getClassLoader().toString()); if(cls1 ! = null){ try { Object obj = cls1.newInstance(); //SpeakTest1 speak = (SpeakTest1) obj; //speak.speak(); Method method = cls1.getDeclaredMethod("speak",null); // Invoke method.invoke(obj, null) by reflection; } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } DiskClassLoader diskLoader = new DiskClassLoader("D:\\lib"); System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString()); new Thread(new Runnable() { @Override public void run() { System.out.println("Thread "+Thread.currentThread().getName()+" classloader: "+Thread.currentThread().getContextClassLoader().toString()); // TODO auto-generated method stub try {// Loading the class file // thread.currentThread ().setContextClassLoader(diskLoader); //Class c = diskLoader.loadClass("com.frank.test.SpeakTest"); ClassLoader cl = Thread.currentThread().getContextClassLoader(); Class c = cl.loadClass("com.frank.test.SpeakTest"); // Class c = Class.forName("com.frank.test.SpeakTest"); System.out.println(c.getClassLoader().toString()); if(c ! = null){ try { Object obj = c.newInstance(); //SpeakTest1 speak = (SpeakTest1) obj; //speak.speak(); Method method = c.getDeclaredMethod("speak",null); // Invoke the Test say method by reflection (obj, null); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }).start();Copy the code

The results are as follows:

We can get the following information:

  1. DiskClassLoader1 Successfully loads the speakTest. class file and executes it successfully.
  2. The ContextClassLoader for the child thread is AppClassLoader.
  3. The AppClassLoader cannot load speakTest. class content already loaded in the parent thread.

Let’s modify the code to add this sentence at the beginning of the child thread.

Thread.currentThread().setContextClassLoader(diskLoader1);
Copy the code

The results are as follows:

You can see that the ContextClassLoader for the child thread becomes DiskClassLoader.

Continue to change the code:

Thread.currentThread().setContextClassLoader(diskLoader);
Copy the code

Results:

DiskClassLoader1 and DiskClassLoader load speakTest. class files in their respective paths. Their class names are the same as com.frank.test.SpeakTest, but their execution results are different. Because their actual content is different.

Context ClassLoader when to use

In fact, I am not very clear about this. My major is Android, and I study ClassLoader to better study Android. The online answer is to adapt to Web services framework software such as Tomcat. The main purpose is to load different apps. Because the loaders are different, the classes generated after loading the same class file are not equal. If you want to know more details, please refer to the relevant materials.

conclusion

  1. ClassLoader is used to load class files.
  2. The built-in ClassLoader uses parental delegation to load classes and resources in the specified path.
  3. You can customize the ClassLoader to override the findClass() method in general.
  4. The ContextClassLoader is thread-specific and can be retrieved and set, bypassing the parent delegate mechanism.