Why do I need class isolation loading
In the process of project development, we need to rely on different versions of middleware dependency packages to adapt to different middleware servers
If the versions of these middleware dependency packages are not downward compatible, the high version dependency cannot connect to the low version server. On the contrary, the low version dependency cannot connect to the high version server
Two versions of middleware dependencies cannot be introduced into the project at the same time, which will inevitably lead to class loading conflict and the normal execution of the program
Solution
1. Plug in package development: make different versions of dependencies into different plug-in packages instead of introducing dependencies directly in the project, so that different dependent versions are different plug-in packages
2. Package of plug-in package: package the plug-in package into all the third-party library dependencies
3. Plug in package loading: the main program loads different plug-in packages according to the middleware version to execute business logic
Plug in package development
Common lang3 dependency is taken as an example here
Create a new Maven project, develop a plug-in package, and introduce middleware dependency. The version dependent in the plug-in package is 3.11
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.11</version> </dependency>
Get the full path of the StringUtils class of Commons lang3. The code is as follows:
public class PluginProvider { public void test() { // Gets the current class loader System.out.println("Plugin: " + this.getClass().getClassLoader()); // Get class full path System.out.println("Plugin: " + StringUtils.class.getResource("").getPath()); } }
Plug in package packaging
Package the plug-in with Maven assembly plugin, and package all the class files in the dependent package into Jar package, POM The XML configuration is as follows:
<plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <artifactId>maven-assembly-plugin</artifactId> <configuration> <descriptorRefs> <descriptorRef>jar-with-dependencies</descriptorRef> </descriptorRefs> </configuration> <executions> <execution> <id>make-assembly</id> <phase>package</phase> <goals> <goal>single</goal> </goals> </execution> </executions> </plugin> </plugins>
After packaging, check XXX jar with dependencies Jar package structure
Main program loading plug-in package
The main program relies on version 3.12.0 of Commons lang3
<dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>3.12.0</version> </dependency>
The parent delegation mechanism of the class loader uses the parent loader to load the class first, and then calls the findClass method when it cannot be loaded
Here, we directly set the parent loader to NULL and reload all classes referenced by the plug-in package Class. The reconstruction code of the Class loader is as follows:
public class PluginClassLoader extends URLClassLoader { public PluginClassLoader(URL[] urls) { // Parental delegation mechanism of class loader // Load with parent loader first class,Call again when the load fails findClass method super(urls, null); } }
Put the plug-in package in the / resources/plugin / directory, as shown in the figure:
The calling plug-in package code is as follows:
public class PluginTester { @PostConstruct public void test() { // Print the current class loader System.out.println("Boot: " + this.getClass().getClassLoader()); // obtain StringUtils Class full path System.out.println("Boot: " + StringUtils.class.getResource("").getPath()); // Simulation call plug-in package testPlugin(); } public void testPlugin() { try { // Load plug-in package ClassPathResource resource = new ClassPathResource("plugin/plugin-provider.jar"); // Print plug-in package path System.out.println(resource.getURL().getPath()); // URLClassLoader classLoader = new URLClassLoader(new URL[]{resource.getURL()}); // Initialize your own ClassLoader PluginClassLoader pluginClassLoader = new PluginClassLoader(new URL[]{resource.getURL()}); // You need to temporarily change the current thread's ContextClassLoader // Avoid problems in middleware code Thread.currentThread().getContextClassLoader()Get class loader // Because they get the current thread's ClassLoader To load class,And the current thread ClassLoader Most likely App ClassLoader Not custom ClassLoader, It may be for security reasons, but this will cause it to be loaded into the startup project class(If any), or other exceptions occur, so we need to temporarily change the current thread's ClassLoader Set as custom ClassLoader,To achieve absolute isolated execution ClassLoader originClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(pluginClassLoader); // Load classes in plug-in package Class<?> clazz = pluginClassLoader.loadClass("cn.codest.PluginProvider"); // Reflection execution clazz.getDeclaredMethod("test", null).invoke(clazz.newInstance(), null); Thread.currentThread().setContextClassLoader(originClassLoader); } catch (Exception e) { e.printStackTrace(); } } }
The results are as follows:
// Print the class loader of the main program
Boot: sun.misc.Launcher$AppClassLoader@18b4aac2
// Print the full path of StringUtils in the main program Boot: file:/D:/Codest/Maven_aliyun/repository/org/apache/commons/commons-lang3/3.12.0/commons-lang3-3.12.0.jar!/org/apache/commons/lang3/
// Print plug-in package path /D:/Codest/Idea/projects/tester/plugin-boot/target/classes/plugin/plugin-provider.jar
// Print the class loader in the plug-in package Plugin: cn.codest.pluginboot.PluginClassLoader@45a4b042
// Print the full path of StringUtils in the plug-in package Plugin: file:/D:/Codest/Idea/projects/tester/plugin-boot/target/classes/plugin/plugin-provider.jar!/org/apache/commons/lang3/
It can be seen from the printing information that the StringUtils loaded in the main program and plug-in package are from the Jar package of 3.12.0 and the classes packaged in the plug-in package respectively.