Thinking logic of computer programs (87) and thinking 87
In the previous section, we discussed dynamic proxies. in the previous sections, we mentioned ClassLoader for many times. This section will discuss in detail the class loading mechanism and ClassLoader in Java.
ClassLoader is the Class for loading other classes. It is responsible for loading bytecode files to the memory and creating Class objects. Like reflection, annotation, and dynamic proxy, we don't need to implement ClassLoader on our own in most application programming.
However, understanding the mechanism and process of class loading helps us better understand the content we introduced earlier and better understand Java. In the reflection section, we have introduced the Class static method Class. forName. Understanding the Class Loader helps us better understand this method.
ClassLoader is generally provided by the system and does not need to be implemented by itself. However, by creating a custom ClassLoader, You can implement some powerful and flexible functions, such:
- Hot deployment: dynamically replaces the implementation of classes without restarting Java programs. For example, JSP Technology in Java Web development uses custom ClassLoader to modify JSP code, the OSGI (Open Service Gateway Initiative) framework uses a custom ClassLoader for dynamic updates.
- The application is modularized and isolated from each other. Different ClassLoader can load the same class but are isolated from each other and do not affect each other. Web application servers, such as Tomcat, use this to manage multiple Web applications in one program. Each Web application uses its own ClassLoader. These Web applications do not interfere with each other. OSGI uses this to implement a dynamic modular architecture. Each module has its own ClassLoader, and different modules can not interfere with each other.
- The default ClassLoader is generally from the local. the class or jar file loads bytecode files. With the custom ClassLoader, we can load bytecode files from other places such as the shared Web server, database, and cache server.
Understanding Custom ClassLoader helps us understand these system programs and frameworks, such as Tomat, JSP, and OSGI. You can also use custom ClassLoader to implement dynamic and flexible functions when necessary.
Next, we will first understand the process of loading classes in Java and the ClassLoader and Class classes. forName: describes a simple application, and then discusses how to implement a custom ClassLoader to demonstrate how to use it for hot deployment.
Basic mechanism and process of class loading
To run a Java program, run the java command to specify the complete class name containing the main method and a classpath, that is, the class path. There can be multiple class paths. For direct class files, the path is the root directory of the class file. For jar packages, the path is the complete name of the jar package (including the path and jar package name ).
During Java runtime, classes are searched and loaded Based on the fully qualified names of the classes. The search method is basically to find the class in the system class and the specified class path. If it is the root directory of the class file, check whether there are corresponding subdirectories and files. If it is a jar file, first decompress the file in the memory and then check whether there is a corresponding class.
The Class responsible for loading the Class is the Class loader. Its input is a fully qualified Class name, and the output is a Class object. There are not only one classloader. Generally, there are three class loaders when the program is running:
- Bootstrap ClassLoader: This loader is part of the implementation of Java virtual machines, not implemented in Java. It is generally implemented in C ++ and is responsible for loading the basic classes of Java, mainly <JAVA_HOME>/lib/rt. jar, the Java class libraries we use daily, such as String and ArrayList, are all in this package.
- Extension ClassLoader: The Implementation class of this loader is sun. misc. launcher $ ExtClassLoader, which is responsible for loading some Java extension classes, generally the jar package in the <JAVA_HOME>/lib/ext directory.
- Application ClassLoader: The Implementation class of this loader is sun. misc. launcher $ AppClassLoader, which is responsible for loading application classes, including self-written and introduced third method class libraries, that is, all classes specified in the class path.
These three class loaders have a certain relationship and can be considered as parent-child relationships. The father of Application ClassLoader is Extension ClassLoader, and the father of Extension is Bootstrap ClassLoader. Note that they are not parent-child inheritance relationships, instead, the sub-ClassLoader has a variable parent pointing to the parent ClassLoader. When the sub-ClassLoader loads a class, it usually loads the class through the parent ClassLoader. Specifically, when loading a class, the basic process is:
- To determine whether a Class has been loaded and loaded, the Class object is directly returned. A Class is only loaded once by a ClassLoader.
- If the object is not loaded, first load the parent ClassLoader. if the object is successfully loaded, the Class object is returned.
- If the parent ClassLoader is not loaded successfully, try to load the class by yourself.
This process is generally called the "parent-parent delegation" model, that is, the parent ClassLoader is preferentially loaded. Why should I load the parent ClassLoader first? In this way, the Java class library can be overwritten. For example, the user program also defines a java class. lang. string, delegates by parent, java. lang. the String is only loaded by Bootstrap ClassLoader, so that the custom String does not overwrite the definition of the Java class library.
It should be noted that although "parent-parent delegation" is a general model, there are some exceptions, such:
- Custom loading sequence: Although not recommended, the custom ClassLoader may not follow the "parent-parent delegation" convention. However, classes starting with "java" cannot be loaded by custom class loaders, which is guaranteed by Java's security mechanism to avoid confusion.
- Mesh loading sequence: In the OSGI framework, the relationship between class loaders is a network. Each OSGI module has a class loader, and different modules may have dependencies, when a module loads a class, it may be loaded from its own module, or it may be assigned to the class loader of other modules.
- A typical example is the Java Naming and Directory Interface, which is a service in Java Enterprise applications.
When a program runs, an Application ClassLoader will be created and used in the program where ClassLoader is located. If this ClassLoader is not specified, this ClassLoader is generally used, this ClassLoader is also known as the System ClassLoader ).
Next, let's take a look at the class-ClassLoader that represents the class loader.
Understanding ClassLoader
Basic usage
ClassLoader is an abstract class. The specific implementation classes of Application ClassLoader and Extension ClassLoader are sun. misc. launcher $ AppClassLoader and sun. misc. launcher $ ExtClassLoader, Bootstrap ClassLoader is not implemented by Java, and there is no corresponding class.
Each Class object has a method to obtain the ClassLoader that actually loads it. The method is:
public ClassLoader getClassLoader()
ClassLoader has a method to obtain its parent ClassLoader:
public final ClassLoader getParent()
If ClassLoader is Bootstrap ClassLoader, the return value is null.
For example:
public class ClassLoaderDemo { public static void main(String[] args) { ClassLoader cl = ClassLoaderDemo.class.getClassLoader(); while (cl != null) { System.out.println(cl.getClass().getName()); cl = cl.getParent(); } System.out.println(String.class.getClassLoader()); }}
Output:
sun.misc.Launcher$AppClassLoadersun.misc.Launcher$ExtClassLoadernull
ClassLoader has a static method to obtain the default system Class Loader:
public static ClassLoader getSystemClassLoader()
ClassLoader has a primary method for loading classes:
public Class<?> loadClass(String name) throws ClassNotFoundException
For example:
ClassLoader cl = ClassLoader.getSystemClassLoader();try { Class<?> cls = cl.loadClass("java.util.ArrayList"); ClassLoader actualLoader = cls.getClassLoader(); System.out.println(actualLoader);} catch (ClassNotFoundException e) { e.printStackTrace();}
It should be noted that, due to the delegate mechanism, the getClassLoader () method of the Class does not necessarily return the ClassLoader that calls loadClass. For example, in the above Code, java. util. arrayList is actually loaded by BootStrap ClassLoader, so the return value is null.
ClassLoader vs Class. forName
In the reflection section, we have introduced two static Class methods forName:
public static Class<?> forName(String className)public static Class<?> forName(String name, boolean initialize, ClassLoader loader)
The first method uses the system class loader for loading. The second method specifies the ClassLoader. The initialize parameter indicates whether to execute class initialization code (such as static statement block) after loading. The default value is not specified.
The loadClass and forName methods of ClassLoader can both load classes. What are their differences? Basically the same, but there is a difference. The loadClass of ClassLoader does not execute class initialization code. Let's look at an example:
public class CLInitDemo { public static class Hello { static { System.out.println("hello"); } }; public static void main(String[] args) { ClassLoader cl = ClassLoader.getSystemClassLoader(); String className = CLInitDemo.class.getName() + "$Hello"; try { Class<?> cls = cl.loadClass(className); } catch (ClassNotFoundException e) { e.printStackTrace(); } }}
Use ClassLoader to load static internal class Hello, Hello has a static statement block, output "hello", run the program, the class is loaded, but no output, that is, the static statement block is not executed. If you replace the loadClass statement:
Class<?> cls = Class.forName(className);
The static statement block is executed, and the screen outputs "hello ".
Implementation Code
Let's take a look at the loadClass code of ClassLoader to further understand its behavior:
public Class<?> loadClass(String name) throws ClassNotFoundException { return loadClass(name, false);}
It calls another loadClass method. Its main code is (some code is omitted and comments are added for ease of understanding ):
Protected Class <?> LoadClass (String name, boolean resolve) throws ClassNotFoundException {synchronized (getClassLoadingLock (name) {// first, check whether the Class has been loaded Class c = findLoadedClass (name ); if (c = null) {// not loaded, assign the parent ClassLoader or BootStrap ClassLoader to load try {if (parent! = Null) {// delegate the parent ClassLoader. The resolve parameter is fixed to false. c = parent. loadClass (name, false);} else {c = findBootstrapClassOrNull (name);} catch (ClassNotFoundException e) {// not found, catch exception, so that you can try to load it yourself} if (c = null) {// you can load it yourself. findClass is the real Loading Method of the current ClassLoader c = findClass (name);} if (resolve) {// link, execute the static statement block resolveClass (c) ;}return c ;}}
The resolve parameter is similar to the Class. the initialize parameter in forName shows that its default value is false. Even if you rewrite loadClass by using a custom ClassLoader and set resolve to true, when it calls the parent ClassLoader, it passes a fixed false value.
FindClass is a protected method. The default Implementation of class ClassLoader is to throw ClassNotFoundException. The subclass should rewrite this method to implement its own loading logic. Next we will look at a specific example.
Class load application-configurable policies
You can use the loadClass or Class. forName of ClassLoader to load classes by yourself. But when do you need to load classes by yourself?
Many applications use interface-oriented programming. There may be many specific implementation classes of interfaces, which are suitable for different scenarios. Which implementation class is used for configuration in the configuration file? By changing the configuration, you can change the program behavior without changing the Code. In the design mode, this is a policy mode. Let's look at a simple example.
Define a service interface IService:
public interface IService { public void action();}
How does the client access the IService instance through this interface? View the configuration file, load it by yourself based on the configuration implementation class, and use reflection to create an instance object. The sample code is as follows:
public class ConfigurableStrategyDemo { public static IService createService() { try { Properties prop = new Properties(); String fileName = "data/c87/config.properties"; prop.load(new FileInputStream(fileName)); String className = prop.getProperty("service"); Class<?> cls = Class.forName(className); return (IService) cls.newInstance(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) { IService service = createService(); service.action(); }}
Example of config. properties:
service=shuo.laoma.dynamic.c87.ServiceB
The code is relatively simple and will not be repeated.
Custom ClassLoader
Basic usage
The powerful feature of the Java class loading mechanism is that we can create custom ClassLoader, which is the basis for Tomcat to implement Application Isolation and support JSP, and OSGI to implement dynamic modularization.
How to customize it? Generally, you can override findClass by inheriting the class ClassLoader. How to Implement findClass? Use your own logic to find the byte format of the class file bytecode. After finding it, use the following method to convert it to a Class Object:
protected final Class<?> defineClass(String name, byte[] b, int off, int len)
Name indicates the class name. B is the byte array that stores bytecode data. valid data starts from off and its length is len.
Let's look at an example:
public class MyClassLoader extends ClassLoader { private static final String BASE_DIR = "data/c87/"; @Override protected Class<?> findClass(String name) throws ClassNotFoundException { String fileName = name.replaceAll("\\.", "/"); fileName = BASE_DIR + fileName + ".class"; try { byte[] bytes = BinaryFileUtils.readFileToByteArray(fileName); return defineClass(name, bytes, 0, bytes.length); } catch (IOException ex) { throw new ClassNotFoundException("failed to load class " + name, ex); } }}
MyClassLoader loads classes from the path under BASE_DIR. It uses the BinaryFileUtils introduced in section 57 to read files and convert them to byte arrays. MyClassLoader does not specify the parent ClassLoader. The default value is the system class loader, that is, the returned value of ClassLoader. getSystemClassLoader (). However, ClassLoader has a Rewritable constructor that can specify the parent ClassLoader:
protected ClassLoader(ClassLoader parent)
Purpose
What is the use of MyClassLoader? Adding BASE_DIR to classpath does not work. Indeed, this is mainly to demonstrate basic usage. In practice, you can obtain the bytes array from the Web server, database, or cache server, this is not what the system class loader can do.
However, instead of placing BASE_DIR in classpath, you can use MyClassLoader to load it. There is indeed a great benefit. You can create multiple MyClassLoader. For the same class, each MyClassLoader can be loaded once, obtain different Class objects of the same Class, for example:
MyClassLoader cl1 = new MyClassLoader();String className = "shuo.laoma.dynamic.c87.HelloService";Class<?> class1 = cl1.loadClass(className);MyClassLoader cl2 = new MyClassLoader();Class<?> class2 = cl2.loadClass(className);if (class1 != class2) { System.out.println("different classes");}
Cl1 and cl2 are two different ClassLoader. class1 and class2 correspond to the same class names, but they are different objects.
What is the purpose of this?
- Isolation can be implemented. A complex program may be organized by module. Different modules may use the same class, but different versions are used. If the same class loader is used, they cannot coexist. Different modules can be isolated by using different class loaders. Tomcat uses it to isolate different Web applications, and OSGI uses it to isolate different modules.
- Hot deployment can be implemented. With the same ClassLoader, the class will only be loaded once. After loading, even if the Class file has changed, the class object will be loaded again, with MyClassLoader, you can create a new ClassLoader first, and then load the Class with it. The obtained Class object is new, so as to achieve dynamic updates.
Next, let's take a look at the hot deployment example.
Custom ClassLoader application-hot deployment
The so-called hot deployment means that, without restarting the application, when the Class definition, that is, after the bytecode file is modified, the object created by the Class can be replaced, how can this be done? Let's use MyClassLoader to look at a simple example.
We use interface-oriented programming to define an interface IHelloService:
public interface IHelloService { public void sayHello();}
The implementation class is shuo. laoma. dynamic. c87.HelloImpl, and the class file is placed in the loading directory of MyClassLoader.
The demo class is HotDeployDemo, which defines the following static variables:
private static final String CLASS_NAME = "shuo.laoma.dynamic.c87.HelloImpl";private static final String FILE_NAME = "data/c87/" +CLASS_NAME.replaceAll("\\.", "/")+".class";private static volatile IHelloService helloService;
CLASS_NAME indicates the implementation class name, FILE_NAME indicates the specific class file path, and helloService is an IHelloService instance.
When the class bytecode represented by CLASS_NAME changes, we want to recreate helloService to reflect the latest code. How can this problem be solved? First, let's look at how the user gets IHelloService:
public static IHelloService getHelloService() { if (helloService != null) { return helloService; } synchronized (HotDeployDemo.class) { if (helloService == null) { helloService = createHelloService(); } return helloService; }}
This is a singleton mode. The code for createHelloService () is:
private static IHelloService createHelloService() { try { MyClassLoader cl = new MyClassLoader(); Class<?> cls = cl.loadClass(CLASS_NAME); if (cls != null) { return (IHelloService) cls.newInstance(); } } catch (Exception e) { e.printStackTrace(); } return null;}
It uses MyClassLoader to load the class and uses reflection to create an instance. It assumes that the implementation class has a public parameter-free constructor.
When calling the IHelloService method, the Client Always obtains the instance object through getHelloService. We simulate a client thread, which keeps getting the IHelloService object, calls its method, and then sleeps for 1 second, the code is:
public static void client() { Thread t = new Thread() { @Override public void run() { try { while (true) { IHelloService helloService = getHelloService(); helloService.sayHello(); Thread.sleep(1000); } } catch (InterruptedException e) { } } }; t.start();}
How does one know that the class file of the class has changed and the helloService object is re-created? We use a separate thread to simulate this process. The code is:
public static void monitor() { Thread t = new Thread() { private long lastModified = new File(FILE_NAME).lastModified(); @Override public void run() { try { while (true) { Thread.sleep(100); long now = new File(FILE_NAME).lastModified(); if (now != lastModified) { lastModified = now; reloadHelloService(); } } } catch (InterruptedException e) { } } }; t.start();}
We use the last modification time of the file to track whether the file has changed. After the file is modified, we call reloadHelloService () to reload the file. The Code is as follows:
public static void reloadHelloService() { helloService = createHelloService();}
It is to use MyClassLoader to re-create HelloService. After the creation, assign the value to helloService. In this way, the next time getHelloService () gets the latest one.
Start the client and monitor threads in the main program. The code is:
public static void main(String[] args) { monitor(); client();}
Replace HelloImpl. class, we can see that the behavior will change. To facilitate the demonstration, we have prepared two different implementation classes HelloImpl_origin.class and HelloImpl_revised.class in the data/c87/shuo/laoma/dynamic/c87/directory, replace in the running process, and the output is different, as shown in:
Run the cp command to modify HelloImpl. class. if the content is the same as that of HelloImpl_origin.class, the output is "hello". If it is the same as HelloImpl_revised.class, the output is "hello revised ".
Complete code and data are available on github.
Summary
This section discusses the class loading mechanism in Java, including the basic process of loading classes in Java, usage of class ClassLoader, and how to create a custom ClassLoader. Two simple application examples are discussed, one is configurable through dynamic loading, and the other is hot deployment through custom ClassLoader.
From Section 84 to this section, we have discussed Multiple Dynamic Features in Java, including reflection, annotation, dynamic proxy, and Class Loader. As an application programmer, most of them are used less often, most of the annotations provided by frameworks and libraries are used, but these features are widely used in various system programs, frameworks, and libraries, understanding these features can help us better understand them and achieve dynamic, universal, and flexible functions as needed.
In the annotation section, we mentioned that annotation is a declarative programming style that improves the expression ability of the Java language. A common requirement in daily programming is text processing, in computer science, there is a technology that greatly improves the expression ability of text processing, that is, regular expressions. Most programming languages support it. what powerful functions does it have?
(As in other chapters, all the code in this section is located at https://github.com/swiftma/program-logicand under Bao shuo.laoma.dynamic.c87)
----------------
For more information, see the latest article. Please pay attention to the Public Account "lauma says programming" (scan the QR code below), from entry to advanced, ma and you explore the essence of Java programming and computer technology. Retain All copyrights with original intent.