Java custom ClassLoader implements isolated loading of plug-in classes

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.

Keywords: Java

Added by rem on Thu, 27 Jan 2022 17:53:20 +0200