Class loader constraints-parental delegation, and break the parental delegation

What is a class loader?

The task of the entire class loading process is very heavy. Although the task is in progress, there is always someone to do it. What the class loader does is the above five steps. (Load, verify, prepare, parse, initialize).

Class loader provided by JDK

Bootstrap ClassLoader (startup)

This is the handle in the class loader, and any class loading behavior must pass through it. Its role is to load core class libraries, that is, rt.jar.resources.jar, charsets.jar, etc. Of course, the path of these jar packages can be specified, and the -Xbootclasspath parameter can complete the specified operation.

This loader is written in C++ and starts with the JVM (that is, the core classes are already loaded when the program starts, and other classes need to be loaded according to different timings).

Extention ClassLoader (extended)

The extended class loader is mainly used to load jar packages and .class files in the lib/ext directory of jdk. Similarly, this directory can be specified through the system variable java.ext.dirs. This loader is a Java class, inherited from URLClassLoader.

Application ClassLoader (application)

This is the default loader for the Java classes we wrote. Sometimes called System ClassLoader. Generally used to load all other jar packages and .class files under the classpath. The code we wrote first tried to load using this class loader.

Custom ClassLoader (custom)

Custom class loader supports some personalized extension functions.

How to determine the uniqueness of a class

If we write a java.lang package in our project, and then rewrite some of the String class behavior. After compiling, it is found that it does not take effect. Of course, JRE classes cannot be overwritten so easily, otherwise they will be used by people with ulterior motives, which is too dangerous.

For any class, its uniqueness in a Java virtual machine needs to be established by the class loader that loads it and the class itself. Each class loader has an independent class name space. This sentence can be expressed in a more general way: comparing whether two classes are "equal" only makes sense if the two classes are owned by the same class loader. Otherwise, even if these two classes originate from the same Class file and are loaded by the same virtual machine, as long as the class loader that loads them is different, the two classes must be unequal.

The "equal" referred to here includes the equals() method, isAssignableFrom() method and isInstance() method of the Class object representing the class, as well as the use of the instanceof keyword to determine the object ownership relationship.

How to determine the uniqueness of the class?

Class fully qualified name + corresponding ClassLoader

So how does the virtual machine choose the class loader corresponding to a class?

The parent delegation mechanism of the rules of the class loader

The parent delegation model requires that in addition to the top-level startup class loader, all other class loaders should have their own parent class loaders. Here, the parent-child relationship between class loaders is generally not realized in the inheritance relationship, but all use the composition relationship (one class holds another class) to reuse the code of the class loader.

Using the parent delegation model to organize the relationship between class loaders, an obvious advantage is that Java classes have a priority hierarchical relationship with its class loaders. For example, the class java.lang.Object is stored in rt.jar. No matter which class loader wants to load this class, it is ultimately delegated to the startup class loader at the top of the model to load it. Therefore, the Object class is a class in the various class loader environments of the program. On the contrary, if there is no parental delegation model, all types of loader will load it by themselves, if the user writes a class called java.lang.Object. In the ClassPath of the program, there will be multiple different Object classes in the system, the most basic behavior in the Java type system cannot be guaranteed, and the application will become chaotic.

Summary: The parent delegation model allows classes to have a priority order, and the class that the subclass loader is responsible for can never interfere with the class that the parent class loader is responsible for, thereby improving system security.

image.png

Understand class loader through source code

We can look at the loadClass method of the ClassLoader class of the JDK code to see the specific loading process. Same as we described. It uses the parent to try to load the class first, and then it's its turn after the parent fails. At the same time, we also noticed that this method can be overridden. That is, the parental delegation mechanism may not be effective.

parent.loadClass represents passing this class upwards.Picture 1.png

The formal parameter name represents the fully qualified name of the class.

The loadClass method is used to find the class loader to load the class based on the fully qualified name of the class. From the source code, it can be seen that when the current class loader has a parent class loader, it will switch to the parent class loader first. Recursively find the top-level parent class loader (Bootstrap ClassLoader). Then the top-most parent class loader determines whether this class can be loaded in turn. Each class will eventually find its own, unique class loader.

The hierarchy of class loaders

Picture 3.png

Through this code, we can understand that from a hierarchical perspective, we can get the conclusion of Bootstrap ClassLoader>Extention ClassLoader>Application ClassLoader.

Moreover , the BootStrapClassLoader class loader is not loaded through JVM, and the bottom layer is implemented in C++.

Break parental delegation

We mentioned earlier that the parent delegation is to achieve hierarchical loading, but why do we have to talk about how to break him? Here, we use two examples to illustrate that parental delegation not only creates isolation between classes and classes, but also causes limitations.

Tomcat class loading mechanism

Tomcat uses the war package for application release, which actually violates the principle of parental delegation. Take a brief look at the hierarchy of tomcat class loader.

Picture 4.png

For some basic classes that need to be loaded, a class loader called WebAppClassLoader will be loaded first. When it cannot be loaded, it will be handed over to the upper ClassLoader for loading. (This order is the opposite of JVM's parental delegation). This loader is used to isolate .class files of different applications. For example, in different versions of the same project, they have no influence on each other. If you still follow the parental delegation model, then the class of the first loaded project will be successfully loaded. The subsequent classes cannot be loaded and an error is reported.

So how to run two incompatible versions of the project in the JVM. Of course, you need a custom class loader to complete.

So how does tomcat break parental delegation. You can see the WebAppClassLoader in the picture. When it loads the .class file in its own directory, it will not be passed to the loader of the parent class. So as to achieve isolation.

Picture 5.png

Picture 6.png

It can be seen that the delegate is false by default, so it will not go to the module that uses the parent parent class loader.

In Tomcat, you can use the classes loaded by SharedClassLoader to achieve sharing, and use the classes loaded by WebAppClassLoader to achieve separation.

But if you write an ArrayList and put it in the WEB-INF/lib directory, it will still not be loaded. Because the classes that have been loaded by the JVM still follow the parental delegation.

SPI mechanism

JDBC

There is an SPI mechanism in Java, the full name is Service Provider Interface, which is a set of APIs provided by Java to be implemented or extended by third parties. It can be used to enable framework extensions and replace components.

This statement may be more obscure. We use the commonly used database driver to load. Before writing a program using JDBC, the following line of code is usually called to load the driver class that needs to be loaded.

Class.forName(“com.mysql.cj.jdbc.Driver”)

This is just an initialization mode. The driver object is displayed and declared through the static code block, and then the information is saved to a List at the bottom. This is an idea of ​​interface programming.

But you will find that even if the line of code Class.forName is deleted, the correct driver class can be loaded. Nothing needs to be done, it is very magical. How is this done?

Picture 7.png

Picture 8.png

MySql driver agent is implemented here.

路径:mysql-connector-java-8.0.11.jar/META-INF/services/java.sql.Driver

The content inside is: com.mysql.cj.jdbc.Driver

Picture 10.png

By creating a file named after the fully qualified name of the interface in the META-INF/services directory (the content is the fully qualified name of the implementation class), this implementation can be automatically loaded, which is SPI. Let me explain in detail below.

Parental delegation solves the problem of consistency of basic types when various loaders cooperate (the more basic classes are loaded by the higher-level loader). The basic type is called "basic". It's because they always exist as APIs that are inherited and called by user code, but there are often no perfect rules for program design. If the basic type needs to call back the user's code, what should be done?

This is not impossible. A typical example is the JNDI service. JNDI is now a standard Java service, and its code is loaded by the startup class loader (added to rt.jar in JDK1.3) , It must be a very basic type of data Java. But the purpose of JNDI is to find and centrally manage resources. It needs to call the code of the JNDI Service Provider Interface (SPI) implemented by other vendors and deployed under the ClassPath of the application. Now the problem is coming. It is absolutely impossible to recognize and load these class loaders. Our goal is now clear: we need to load these classes by using the class loader responsible for loading these classes. So how do you get it?

In order to solve this dilemma, the Java team introduced a less elegant design: thread context class loader (we must call the code of the parent class loader in our own code, so the context class loader is our application class loader器). This class loader can be set by the setContext-ClassLoader() method of the java.lang.Thread class. If it is not set when the thread is created, it will inherit one from the parent thread. If it is not set in the global scope of the application, then this class loader is the application by default, and the class loader is the application by default Program class loader.

With the contextual class loader, the program uses the ability of "fraud". The JNDI service uses this thread context class loader to load the required SPI service code, which is a behavior in which the parent class loader requests the child class loader to complete the class loading. This behavior actually breaks through the hierarchy of parental delegation to reverse the use of the class loader, which also violates its general principles. But this is also helpless. The loading of SPI designed in Java is basically done in this way.

Picture 9.png

This approach also breaks the parental delegation.

From the JNDI source code, understand the SPI mechanism

Both DriverManager class and ServiceLoader class belong to rt.jar. Their class loader is Bootstrap ClassLoader, and the database driver for specific operations comes from business code. This startup class loader cannot be loaded. What should I do at this time?

Java's own management service JNDI is placed in rt.jar and loaded by the startup class loader. Take the database management JDBC as an example.

Java provides a Dirver interface for database operations:

Picture 11.png

Picture 12.png

Most of the code is omitted here, but we can find that before using the database driver, you must use registerDriver() to register in the DriverManager of BootstrapClassLoader before it can be used normally.

Does not destroy parental delegation (not applicable to JNDI services)

Let's take a look at how the mysql driver is loaded.

Picture 13.png

The core is that Class.forName() triggers the loading of the mysql driver. Let's look at the connection of mysql to Driver.

Picture 14.png

As you can see, Class.forName() actually triggers a static code block, and then registers a mysql Driver implementation with DriverManager.

At this time, when we get the connection through DriverManager, we only need to establish all the current Driver implementations, and then select one to establish a connection.

Undermining the parental delegation model

After JDBC4.0, it began to support the use of spi to register this Driver. The specific method is to specify which Driver is currently used in the META-INF/services/java.sql.Driver file in the mysql jar package, and then Just connect directly when you use it.

Picture 15.png

Here we find that the connection is directly obtained, and the registration process of Class.forName() is omitted.

Now we analyze the basic process of using this spi service mode:

1. Obtain the specific implementation class name "com.mysql.jdbc.Driver" from the META-INF/services/java.sql.Driver file.

2. Load this class, here must only be loaded with class.forName ("com.mysql.jdbc.Driver")

Then the problem is coming, Class.forName() is loaded with the caller’s ClassLoader, the caller’s DriverManager is in rt.jar, ClassLoader is the startup class loader, and com.mysql.jdbc.Driver is definitely not there< JAVA_HOME>/lib, so the class in mysql must not be loaded. This is the limitation of parent delegation, the parent class loader cannot load the class in the child class loader path.

So how can the problem be solved? According to the current situation, this mysql driver can only be loaded by the application class loader, so we only need to get the application class loader in the method of starting the class loader. Then load it through it. This is the so-called thread context loader. (The thread is the main thread, and the context in which the main thread is located is loaded by the application class loader),

The thread context class loader can be set through the Thread.setContextClassLoader() method. If there is no special setting, it will be inherited from the parent class. Generally, the application class loader is used by default

Obviously, the parent class loader of the thread context class loader can be loaded by calling the child class loader, which breaks the principle of the parent delegation model.

Now let's take a look at how DriverManager uses the context class loader to load the Driver class in the third-party jar package.

Picture 23.png

When in use, directly calling the DriverManager.getConn() method will naturally trigger the execution of the static code block. Start to load the driver.

Then the concrete realization of ServiceLoader.load().

Picture 22.png

It can be seen that the core is to get the thread context class loader, and then construct a ServiceLoader, the subsequent specific search process will not be analyzed, as long as the ServiceLoader has been found to have got the thread context class loader.

Next to me, there is a sentence driversIterator.next() in the LoadInitialDrivers() method of DriverManager; its specific implementation is as follows:

Picture 16.png

Did you see it? See the familiar Class.forName() method. In fact, the DriverManager.getConnection() method originally uses the DriverManager class in the bootstrap class loader. Then in the BootStrap loader through the context environment (after all, we write code must be loaded in the application or lower-level custom class loader) to obtain the application class loader. Going around in a big circle, the goal is very clear, which is to achieve a reasonable call to the Class.forName() method in the parent class loader!

So far, we have successfully obtained the application class loader through the thread context class loader (, and at the same time, we have also found the specific implementation class name of the driver registered by the manufacturer in the child jar package, so that we can successfully enter the rt.jar The DriverManager in the package successfully loaded the classes placed in the third-party application package.

to sum up

Looking at the entire mysql driver loading process:

1. Get the thread context class loader, and thus get the application class loader (or a custom class loader).

2. Obtain the specific implementation class name "com.mysql.jdbc.Driver" (ServiceLoader.load(Driver.class)) from the META-INF/services/java.sql.Driver file

3. The thread context loader is used to load the Driver class, thus avoiding the drawbacks of the parent delegation model.

Obviously, the use of this spi service by the mysql driver really breaks the parental delegation model. After all, the parent class loader has loaded the classes in the child path.

Handwriting destroys parental delegation

In fact, in the above case, we have explained how mysql breaks the parental delegation. So can we try to break the parental delegation by ourselves?

First, we need to create a class ShuangqinTest that will only be loaded by the Extension class loader.

Picture 17.png

Pack this class into a jar package and put it in the jdk\jre\lib\ext directory for identification by the Extension class loader.

Picture 20.png

At this time, this jar file is also introduced in the project directory, as shown in the figure.

Picture 18.png

In this way, the ShuangqinTest class will only be loaded by the Extension class loader!

Define the Test method in this class and write down the class path loaded by the application loader. The locadExtClass method is similar to the Test class, except that the path of the class is changed to a parameter.

Then get the application class loader through the context Thread.currentThread().getContextClassLoader(). Later, by using reflection, you can call the methods in the class loaded by the application class loader in the class loaded by the Extension class loader. Let's take a look at the specific operation results.

Picture 22.png

Picture 23.png

You can try it yourself to deepen your idea of ​​breaking the class loader solution.

OSGI (understand)

OSGI was once very popular, and Eclipse is the basis for using OSGI as a plug-in system. OSGI is the specification of the service platform, determined to be used in systems that require long-term operation, dynamic updates, and minimal damage to the environment.

The OSGI specification defines a lot about the package life cycle, as well as the way of interacting with the infrastructure and binding packages. These rules, through the use of special Java class loader to enforce, more overbearing.

For example, in general Java applications, there is no doubt that all classes in the classpath are visible to other classes. However, the OSGI class loader restricts the interaction of these classes based on the OSGI specification and the options specified in the manifest.mf file of each binding package, which makes the programming style very weird. But it is not difficult to imagine that this counter-intuitive loading method is implemented by a dedicated class loader.

With the development of JPMS (introduced by JDK9, it is designed to implement a standard module system for the Java SE platform)

Combination and inheritance difference and connection between class and class relationship

combination

It is enough to declare an object of another class as its member variable in the current class.

Picture 24.png

advantage:

1. Low coupling. Changes in the internal details of the referenced object will not affect the use of the current object.

2. Can be dynamically bound. The current object can be dynamically bound to the contained objects at runtime.

Disadvantage

It is easy to produce too many objects.

inherit

Inheritance means that the subclass inherits the characteristics and behaviors of the parent class, so that the subclass object has the instance domain and methods of the parent class, or the subclass inherits methods from the parent class, so that the subclass has the same behavior as the parent class.

The point of inheritance is not to "provide methods for the new class", but to express the relationship between the new class and the base class-"the new class is a form of the existing class".

Picture 25.png

Inherited characteristics:

1. The child class has the non-private properties and methods of the parent class.

2. Subclasses can have their own properties and methods to extend the parent class.

3. The subclass can implement the method of the parent class in its own way (method overriding).

4. Single inheritance, a subclass has only one parent class.

5. Multiple inheritance, that is, a child class inherits a parent class, and this parent class can also inherit its parent class.

6. Improve the coupling between classes ----- the shortcomings of inheritance (combination can reduce the degree of coupling)

7. Destroy the encapsulation, in order to reuse the code, the subclass may have functions that it should not have---the shortcoming of inheritance.

Summary: If it is just to reuse the method, it is recommended to give priority to the combination method.

 

Guess you like

Origin blog.csdn.net/weixin_47184173/article/details/109803737